Prologue: Passing Dispatch as a Props

Start with this CodeSandbox.

Before we dive into the Context API, let's wire this up so that we can update the state.

If you look at Application.tsx, it almost looks like we have no TypeScript at all.

const Application = () => {
  const [rgb, dispatch] = React.useReducer(reducer, {
    red: 0,
    green: 0,
    blue: 0,
  });

  return (
    <main style={{ borderColor: toRGB(rgb) }}>
      <ColorSwatch rgb={rgb} />
      <ColorInputs rgb={rgb} />
      <ColorSliders rgb={rgb} />
    </main>
  );
};

But, if you hover over the rgb and the dispatch coming out of useReducer, you can see both definitely have some type information associated with them.

const rgb: RGBColorType;
const dispatch: React.Dispatch<AdjustmentAction>;

RGBColorType is freqently used in this application, so it's commonly stored in the src/types directory.

export interface RGBColorType {
  red: number;
  green: number;
  blue: number;
}

If we look at the reducer, we can see that we have the type definition for our actions.

export type AdjustmentAction = {
  type: "ADJUST_RED" | "ADJUST_GREEN" | "ADJUST_BLUE";
  payload: number;
};

export const reducer = (
  state: RGBColorType,
  action: AdjustmentAction
): RGBColorType => {
  if (action.type === "ADJUST_RED") {
    return { ...state, red: action.payload };
  }

  if (action.type === "ADJUST_GREEN") {
    return { ...state, green: action.payload };
  }

  if (action.type === "ADJUST_BLUE") {
    return { ...state, blue: action.payload };
  }

  return state;
};

We're going to pass dispatch down into a component just to see how it works. Technically, we need to do it twice. I'm not going to because I know I am going to refactor it in a bit, but if you want to as an exercise to the reader, the you're more than welcome to.

Let's head into ColorSliders.tsx.

We want to use the current type that we pulled from RGBColorType but we also want to extend it to support our dispatch.

I purposely used an interface instead of a type this time so that I could demonstrate the difference in how you extend each of them.

interface ColorSidersProps extends RGBColorType {
  dispatch: Dispatch<AdjustmentAction>;
}

Okay, now we can pass in dispatch to our ColorSliders component in Application.tsx;

<ColorSliders {...rgb} dispatch={dispatch} />

In ColorSliders.tsx, we can wire up the ability to dispatch actions.

const adjustRed = (event: ChangeEvent<HTMLInputElement>) => {
  dispatch({ type: "ADJUST_RED", payload: +event.target.value });
};

const adjustGreen = (event: ChangeEvent<HTMLInputElement>) => {
  dispatch({ type: "ADJUST_GREEN", payload: +event.target.value });
};

const adjustBlue = (event: ChangeEvent<HTMLInputElement>) => {
  dispatch({ type: "ADJUST_BLUE", payload: +event.target.value });
};

There are certainly some clever ways to DRY up this code, but I'll save that for a functional programming workshop.

We'd ideally like to pass these in as onChange props. They've got everything they need.

<ColorSlider
  id="red-slider"
  label="Red"
  value={red}
  onChange={adjustRed}
/>
<ColorSlider
  id="green-slider"
  label="Green"
  value={green}
  onChange={adjustGreen}
/>
<ColorSlider
  id="blue-slider"
  label="Blue"
  value={blue}
  onChange={adjustBlue}
/>

But despite the fact that these ColorSlider props are basically some ceremony around an input field, it's a little ridiculous that we need to add props for everything that the underlying component supports.

Fine. We'll go ahead and define a prop for this I guess.

In ColorSlider.tsx:

export interface ColorInputProps {
  id: string;
  label: string;
  value: number;
  onChange: (event: ChangeEvent<HTMLInputElement>) => void;
}

export const ColorSlider = ({
  id,
  label,
  value,
  onChange,
}: ColorInputProps) => {
  return (
    <div className="color-slider">
      <label htmlFor={id}>{label}</label>
      <input
        id={id}
        type="range"
        min="0"
        max="255"
        value={value}
        onChange={onChange}
      />
    </div>
  );
};

Okay, great, it works. Let's slowly fix this up.

In the next few sections, we're going to:

  • Refactor this a bit using the Context API.
  • Create an abstraction for the two types of input fields on this page.
  • Fix a bug that hasn't appeared yet with our sliders.
  • Fix that annoying issue where we find outselves reimplementing the type properties of an input field.

This is where we are at the end of this section.

Where Are We Now?

  • examples/23-color-swatch-passing-dispatch
  • projects/color-swatch-base on the color-swatch-passing-dispatch branch.
  • CodeSandbox