Custom Hook for Context API
In this article, I will describe how and why it's better to use a custom hook for Context API (NOT useContext)
I want to focus on improving the usual way of working with useContext() hook. So, firstly I will provide the code for 'default implementation'. Then discuss some issues and finally provide a way to improve default implementation with a custom hook.
Default implementation
Create the context:
1import { createContext, useState } from 'react';
2
3export const ThemeContext = createContext(null);
4
5export default function ThemeContextProvider({ children }) {
6 const [theme, setTheme] = useState('light');
7 return (
8 <ThemeContext.Provider
9 value={{
10 theme,
11 setTheme,
12 }}>
13 {children}
14 </ThemeContext.Provider>
15 );
16}
Wrap components with a ThemeContextProvider
In the code below Header and Selector components will have access to ThemeContext:
1import Header from '../../components/header';
2import Selector from '../../components/selector';
3import ThemeContextProvider from '../../context/themeContext';
4
5export default function Home() {
6 return (
7 <ThemeContextProvider>
8 <>
9 <Header />
10 <Selector />
11 </>
12 </ThemeContextProvider>
13 );
14}
Use context
Use context in the Header component:
1import { useContext } from 'react';
2import { ThemeContext } from '../../context/themeContext';
3
4const Header = () => {
5 const context = useContext(ThemeContext);
6 if (!context) {
7 throw new Error(
8 'useThemeContext must be used within a ThemeContextProvider'
9 );
10 }
11 const { theme } = context;
12 return (
13 <header
14 style={{
15 backgroundColor: theme === 'light' ? 'blue' : 'black',
16 color: 'white',
17 }}>
18 {theme}
19 </header>
20 );
21};
22
23export default Header;
Issues
- In every component where we want to use ThemeContext, we need to carry around this variable and use it for useContext manually:
- In every component where we want to use ThemeContext, we need to carry around this variable and use it for useContext manually:
- We need to check the context for the null value in every component where we want it to be used:
1if (!context) {
2 throw new Error(
3 'useThemeContext must be used within a ThemeContextProvider'
4 );
5 }
Better way
We can separate useContext() and null check into the custom hook:
1export function useThemeContext() {
2 const context = useContext(ThemeContext);
3
4 if (!context) {
5 throw new Error(
6 'useThemeContext must be used within a ThemeContextProvider'
7 );
8 }
9
10 return context;
11}
Create context (js):
Now our context file looks like this:
1import { createContext, useState, useContext } from 'react';
2
3export const ThemeContext = createContext(null);
4
5export default function ThemeContextProvider({ children }) {
6 const [theme, setTheme] = useState('light');
7 return (
8 <ThemeContext.Provider
9 value={{
10 theme,
11 setTheme,
12 }}>
13 {children}
14 </ThemeContext.Provider>
15 );
16}
17
18export function useThemeContext() {
19 const context = useContext(ThemeContext);
20
21 if (!context) {
22 throw new Error(
23 'useThemeContext must be used within a ThemeContextProvider'
24 );
25 }
26
27 return context;
28}
Create context (using typescript):
In case you are using typescript in your application:
1import React, { createContext, useState, useContext } from 'react';
2
3export enum THEME_COLOR {
4 LIGHT = 'light',
5 DARK = 'dark',
6}
7
8type ThemeContextProviderProps = {
9 children: React.ReactNode;
10};
11
12type ThemeColorScheme = THEME_COLOR.LIGHT | THEME_COLOR.DARK;
13
14type ThemeContext = {
15 theme: ThemeColorScheme;
16 setTheme: React.Dispatch<React.SetStateAction<ThemeColorScheme>>;
17};
18
19export const ThemeContext = createContext<ThemeContext | null>(null);
20
21export default function ThemeContextProvider({
22 children,
23}: ThemeContextProviderProps) {
24 const [theme, setTheme] = useState<ThemeColorScheme>(THEME_COLOR.LIGHT); // default
25
26 return (
27 <ThemeContext.Provider value={{ theme, setTheme }}>
28 {children}
29 </ThemeContext.Provider>
30 );
31}
32
33export function useThemeContext() {
34 const context = useContext(ThemeContext);
35
36 if (!context) {
37 throw new Error(
38 'useThemeContext must be used within a ThemeContextProvider'
39 );
40 }
41
42 return context;
43}
Use context
In child components, we only need to use our custom hook:
1import { useThemeContext } from '../context/themeContext';
2
3const Header = () => {
4 const { theme } = useThemeContext();
5 return (
6 <header
7 style={{
8 backgroundColor: theme === 'light' ? 'blue' : 'black',
9 color: 'white',
10 }}>
11 {theme}
12 </header>
13 );
14};
15
16export default Header;
Source code
The source code you can find here.
Have some questions regarding this article?
Get in touch and let me know how I can help.
Fill out the form and I'll be in touch as soon as possible