React Context with TypeScript: Part 4 - Creating a context with no default and no undefined check
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()!;
A quick solution
A quick solution to the problem is to use the not-null assertion operator and remove undefined
from the context type.
const ThemeContext = React.createContext<
ThemeContextType
>(undefined!);
This works nicely because consuming code doesn’t need undefined
checks. However, we are still passing a default.
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!
Did you find this post useful?
Let me know by sharing it on Twitter.If you to learn more about using TypeScript with React, you may find my course useful: