This is the final post in a series of posts on React context with TypeScript. In the previous post, we consumed a context in a class component. In this post, we will learn how to create a context without having to pass a default and then do any undefined checks when consuming it.
- Part 1 - Simple context with function components
- Part 2 - Complex context with function components
- Part 3 - Context with class components
- Part 4 - Creating a context with no default and no undefined check (this post)
The problem
The type for createContext requires a default value to be passed into it, but often it doesn't make sense to pass a default. So, we end up passing undefined as the default:
const ThemeContext = React.createContext<
ThemeContextType | undefined
>(undefined);
... and then checking for undefined everywhere we consume it:
const { theme, setTheme } = useTheme()!;
Creating a wrapper for creating a context
A solution is to create a wrapper around createContext that deals with the default and the undefined check:
export function createCtx<ContextType>() {
const ctx = React.createContext<
ContextType | undefined
>(undefined);
function useCtx() {
const c = React.useContext(ctx);
if (!c)
throw new Error(
"useCtx must be inside a Provider with a value"
);
return c;
}
return [useCtx, ctx.Provider] as const;
}
This function first creates the context with the generic type passed into it with undefined as its default value.
A nested function is then defined, which wraps the useContext hook. A variable c is assigned to the return value of the useContext hook which is the generic type passed in or undefined:
We then throw an error if c is falsy, which deals with the undefined check. This means that when c is returned from the nested function, it can't undefined and is only the generic type we passed in:
Notice also we use a const assertion (as const) on the last line to ensure TypeScript infers a tuple type rather than an array of union types.
Creating a context
We can now use our createCtx function to create a context rather than React's createContext:
const [useTheme, CtxProvider] = createCtx<
ThemeContextType
>();
Creating a provider
Our createCtx function returns a tuple, which contains a provider component in the second element (CtxProvider). We can the create our specific provider component containing our required state:
export const ThemeProvider = ({
children
}: Props) => {
const [theme, setTheme] = React.useState(
"white"
);
...
return (
<CtxProvider value={{ theme, setTheme }}>
{children}
</CtxProvider>
);
};
This can then be placed at the appropriate position in the component tree:
export const App = () => (
<ThemeProvider>
<Header />
</ThemeProvider>
);
Consuming the context
Our createCtx also returns a hook (useTheme) in the tuples first element. We can use this without having to do any undefined checks:
const Header = () => {
const { theme, setTheme } = useTheme();
return (
<div style={{ backgroundColor: theme }}>
<select
value={theme}
onChange={e =>
setTheme(e.currentTarget.value)
}
>
<option value="white">White</option>
<option value="lightblue">Blue</option>
<option value="lightgreen">Green</option>
</select>
<span>Hello!</span>
</div>
);
};
Neat!
A full working implementation is available by clicking the link below. Give it a try and change the theme value and see the background change color.
Wrap up
The createCtx function is a generic function that can be used to create contexts for many situations. It simplifies consuming code because checks for undefined are not necessary.
That concludes this series of posts on React context with TypeScript. I hope you enjoyed it!
Originally published at https://www.carlrippon.com/react-context-with-typescript-p4/ on Mar 10, 2020.


Top comments (0)