The Context API with State Management
This is our starting point.
Next, let's move our state management into a Context and see if we have any new issues to deal with.
Let's start by pulling in everything we need.
import { RGBColorType } from "./types";
const initialState: RGBColorType = {
red: 0,
green: 0,
blue: 0,
};
Our context typing basically needs to be something like this:
interface RGBContextType extends RGBColorType {
dispatch: React.Dispatch<AdjustmentAction>;
}
But, we have a problem when we pass in the default value. We don't have a dispatch
function yet that we can pass in as a default value. Contexts can only be created outside of components and useReducer
—which we need to create a dispatch
function—can only be used inside of a component.
So, this won't make TypeScript happy:
export const RGBContext = React.createContext<RGBContextType>(null);
This is a bit of a conundrum, eh? Let's cheat for now.
export const RGBContext = React.createContext<any>(null);
Not great, but we'll fix this later once we get the rest of it wired up.
export const RGBContextProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [rgb, dispatch] = React.useReducer(reducer, {
red: 0,
green: 0,
blue: 0,
});
return (
<RGBContext.Provider
value={{
...rgb,
dispatch,
}}
>
{children}
</RGBContext.Provider>
);
};
Cool. Now, let's wrap that around the Application
component so that we can use it.
In ColorSliders.tsx
:
export const ColorSliders = () => {
const { red, green, blue, dispatch } = useContext(RGBContext);
// …
};
We'll need to do something similar in the ColorSwatch
since it depends on that state as well.
export const ColorSwatch = () => {
const { red, green, blue } = useContext(RGBContext);
return (
<div
className="color-swatch"
style={{
backgroundColor: `rgb(${red}, ${green}, ${blue})`,
}}
></div>
);
};
Okay, but now we have to deal with our type issue. any
is insidious. There is literally no typesafety with our context values. It's like we're writing JavaScript or something.
Hypothesis One: Use a Fallback Value
We could try something like this in context.tsx
:
export const RGBContext = React.createContext<RGBContextType | null>(null);
This will make everyone happy in the context.tsx
file. But, what about if we go into ColorSwatch.tsx
.
export const ColorSwatch = () => {
// const { red, green, blue } = useContext(RGBContext);
const value = useContext(RGBContext);
return (
<div
className="color-swatch"
style={{
backgroundColor: `rgb(${value?.red}, ${value?.green}, ${value?.blue})`,
}}
></div>
);
};
Since it can be null
, TypeScript is trying to make us write better code.
Hypothesis Two: Insist You Know What You're Doing
This is all kind of silly. We know what we're doing.
export const RGBContext = React.createContext<RGBContextType>(
initialState as RGBContextType
);
This basically saying. Listen. We know what we're doing here. We're going to have the dispatch
function by the time we use these components. Just pretend that this object fits the type—even if it doesn't.
This will get us more protection than any. That's for sure. But, it's not perfect.
To Be Continued…
There is another way to do this, that has some advantages, but we need to learn a little bit more first about TypeScript before we can dive in.
For now, the final state for this section is here.
Where Are We Now?
projects/color-swatch-base
on thecolor-swatch-with-state-context
branchexamples/25-color-swatch-with-state-context
- [CodeSandbox]