Context
Basic example
Here's a basic example of creating a context containing the active theme.
import { createContext } from "react";
type ThemeContextType = "light" | "dark";
const ThemeContext = createContext<ThemeContextType>("light");
Wrap the components that need the context with a context provider:
import { useState } from "react";
const App = () => {
const [theme, setTheme] = useState<ThemeContextType>("light");
return (
<ThemeContext.Provider value={theme}>
<MyComponent />
</ThemeContext.Provider>
);
};
Call useContext
to read and subscribe to the context.
import { useContext } from "react";
const MyComponent = () => {
const theme = useContext(ThemeContext);
return <p>The current theme is {theme}.</p>;
};
Without default context value
If you don't have any meaningful default value, specify null
:
import { createContext } from "react";
interface CurrentUserContextType {
username: string;
}
const CurrentUserContext = createContext<CurrentUserContextType | null>(null);
const App = () => {
const [currentUser, setCurrentUser] = useState<CurrentUserContextType>({
username: "filiptammergard",
});
return (
<CurrentUserContext.Provider value={currentUser}>
<MyComponent />
</CurrentUserContext.Provider>
);
};
Now that the type of the context can be null
, you'll notice that you'll get a 'currentUser' is possibly 'null'
TypeScript error if you try to access the username
property. You can use optional chaining to access username
:
import { useContext } from "react";
const MyComponent = () => {
const currentUser = useContext(CurrentUserContext);
return <p>Name: {currentUser?.username}.</p>;
};
However, it would be preferable to not have to check for null
, since we know that the context won't be null
. One way to do that is to provide a custom hook to use the context, where an error is thrown if the context is not provided:
import { createContext } from "react";
interface CurrentUserContextType {
username: string;
}
const CurrentUserContext = createContext<CurrentUserContextType | null>(null);
const useCurrentUser = () => {
const currentUserContext = useContext(CurrentUserContext);
if (!currentUserContext) {
throw new Error(
"useCurrentUser has to be used within <CurrentUserContext.Provider>"
);
}
return currentUserContext;
};
Using a runtime type check in this will has the benefit of printing a clear error message in the console when a provider is not wrapping the components properly. Now it's possible to access currentUser.username
without checking for null
:
import { useContext } from "react";
const MyComponent = () => {
const currentUser = useCurrentUser();
return <p>Username: {currentUser.username}.</p>;
};
Type assertion as an alternative
Another way to avoid having to check for null
is to use type assertion to tell TypeScript you know the context is not null
:
import { useContext } from "react";
const MyComponent = () => {
const currentUser = useContext(CurrentUserContext);
return <p>Name: {currentUser!.username}.</p>;
};
Another option is to use an empty object as default value and cast it to the expected context type:
const CurrentUserContext = createContext<CurrentUserContextType>(
{} as CurrentUserContextType
);
You can also use non-null assertion to get the same result:
const CurrentUserContext = createContext<CurrentUserContextType>(null!);
When you don't know what to choose, prefer runtime checking and throwing over type asserting.