logo icon

OKSANA

KOROBANOVA

Custom Hook for Context API img
Back to all posts

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)


Date:


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

By sending form I agree that Oksana Korobanova saves and handles my personal information. See the privacy policy