useEffect.dev
← Back to lessons

Lesson 14. Better hooks with TypeScript 2. Typing contexts

To type your contexts, give the type when you call createContext. It has to be the full type of the context’s value. So if your context contains a theme value and two ways to set it, it can look like that:

type Theme = 'light' | 'dark'
const ThemeContext = createContext<{
theme: Theme
setTheme: (theme: SetStateAction<Theme>) => void
toggle: () => void
}>({
theme: 'light',
setTheme: () => {},
toggle: () => {},
})

Note that when using TypeScript, you need to give createContext a default value with the same type as the context. In my opinion, this is a little annoying because this default value may never be used if you pass the initial value as a prop to the Provider component. This is why here we use an object with empty functions as default value.

With a typed context, the hook(s) you’ll create to access it will be automatically typed by TypeScript, meaning that you shouldn’t have to give any explicit type:

const useTheme = () => useContext(ThemeContext)
const ThemeSwitcher = () => {
const { theme, toggle } = useTheme()
return (
<p>
Theme: {theme} <button onClick={() => toggle()}>Toggle</button>
</p>
)
}

Last piece of the puzzle: if you create a custom provider component for your context, the value you pass to the context’s provider must match the context’s type.

const ThemeProvider = ({
defaultTheme,
children,
}: {
defaultTheme: Theme
children: ReactNode
}) => {
const [theme, setTheme] = useState(defaultTheme)
const toggle = () => setTheme((th) => (th === 'light' ? 'dark' : 'light'))
return (
<ThemeContext.Provider value={{ theme, setTheme, toggle }}>
{children}
</ThemeContext.Provider>
)
}
const App = () => {
return (
<ThemeProvider defaultTheme="light">
<ThemeSwitcher />
</ThemeProvider>
)
}