Solving for Context API Edge Cases
Now that we know a little bit more about generics, we can make an abstraction for solving for what to do if our context value isn't set yet.
Make a new file called create-context.tsx
:
import * as React from "react";
export function createContext<A extends {} | null>() {
const context = React.createContext<A | undefined>(undefined);
const useContext = () => {
const c = React.useContext(context);
if (c === undefined)
throw new Error("useContext must be inside a Provider with a value");
return c;
};
return [useContext, context.Provider] as const;
}
This will make a context and give us a hook that will always check to see if it's defined—thus making TypeScript happier.
Pull it into context.tsx
.
import { createContext } from "./create-context";
And now, swap it in.
export const [useContext, Provider] = createContext<RGBContextType>();
We need to update our provider a bit since we have a different signature as we've hidden away our actual context object.
export const RGBContextProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [rgb, dispatch] = React.useReducer(reducer, {
red: 0,
green: 0,
blue: 0,
});
return (
<Provider
value={{
...rgb,
dispatch,
}}
>
{children}
</Provider>
);
};
And now, we can use it in our component.
const { red, green, blue, dispatch } = useContext();
We also need to swap it out in ColorSwatch.tsx
:
import { useContext } from "./context";
export const ColorSwatch = () => {
const { red, green, blue } = useContext();
return (
<div
className="color-swatch"
style={{
backgroundColor: `rgb(${red}, ${green}, ${blue})`,
}}
></div>
);
};
Thoughts
This is more correct than using as
to tell TypeScript that we know what we're doing.
That said, I'm not sure it's worth all of the extra overhead. You're going to have a specific useContext
hook for every context.
Where Are We Now?
examples/42-context-refactor
projects/color-swatch-base
on theone-last-context-refactor
branch- [CodeSandbox]