React Context with TypeScript: Part 2 - Complex context with function components
This is the second post in a series of posts on React context with TypeScript. In the last post, we created a simple context where TypeScript was able to infer its type.
- Part 1 - Simple context with function components
- Part 2 - Complex context with function components (this post)
- Part 3 - Context with class components
- Part 4 - Creating a context with no default and no undefined check
In this post, we will implement a more complex context where we need to specify the context type explicitly.
Explicitly setting the context type
We are going to enhance the context from the last post so that the theme can be updated by consumers.
In the last post, the type for the context was inferred from the default value, which was a simple string
. The type for our enhanced context is going to be a little more complex:
type ThemeContextType = {
theme: string;
setTheme: (value: string) => void;
};
So, there will be a theme
property containing the current value for the theme and a setTheme
method to update the current theme.
Reacts createContext
function expects us to supply an argument for initial context value. We can supply a default value for the theme
property, but it doesn’t make sense to provide a default implementation for the setTheme
method. So, a simple approach is to pass in undefined
as the initial value:
const ThemeContext = React.createContext(
undefined
);
Let’s see what the inferred type is for the context value:
The type of the context value is inferred to be undefined
if in strict mode or any
if not.
So, ThemeContext
isn’t typed as we require at the moment. How can we explicitly specify the type for the context when using createContext
? Well, createContext
is a generic function. So, we can pass in the type for the context value as a generic parameter:
const context = React.createContext<ContextType>(...)
Therefore, we can type our context is as follows:
const ThemeContext = React.createContext<
ThemeContextType | undefined
>(undefined);
Using the enhanced context in the provider
The modification to the context provider, from the last post, is to the value we provide from it. Instead of a simple string
, it is now an object containing the theme
property and the setTheme
method:
export const ThemeProvider = ({
children
}: Props) => {
...
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
Adding an option to change the theme in the Header
component
The useTheme
custom hook remains the same as in the last post. The App
component remains the same as well.
We are going to change the Header
component though so that the user can change the theme:
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>
);
};
We destructure the current theme value as well as the setTheme
method from the useTheme
hook. Notice that we have put an exclamation mark (!
) after the call to the useTheme
hook to tell the TypeScript compiler that its return value won’t be undefined
.
We have also added a drop down that has options to change the theme to White, Blue, and Green. The value of the drop down is set to the current theme value, and when this is changed, it calls the contexts setTheme
method to update this in the shared state.
The full implementation of the context is available by clicking the link below. Give it a try and change the theme value and see the background change color.
Wrap up
Generally, we will need to create a type for a context and explicitly define the type when the context is created. Often the initial value for the context is undefined
, so the context type is usually a union type that contains undefined
. We need to use the non-null assertion operator (!
) when referencing the context to tell TypeScript that it does have a value.
In the 4th post in this series, we will cover a way of not having to pass a default value to the context and not having to deal with it possibly being undefined
. Before this, in the next post, we will learn how to consume a strongly-typed context with class components.
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: