Site Loader

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.

In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.

I am planning to use below component structure within this article to demo the implementation and usage of context API.

I am trying to implement ThemeChooser and LanguageChooser components which updates the App level state shared between the component tree.

Note before proceed

Context is primarily used when some data needs to be accessible by many components at different nesting levels. Apply it sparingly because it makes component reuse more difficult. (from React docs)

Confused Nick Young | Know Your Meme

Things to consider with Context API

React.createContext(defaultValue)

   
const AppContext = React.createContext(defaultValue);

This is the creation of the context we are going to use throughout the app. Here we can provide defaultValue as well. DefaultValue contains the state data we share across the app. This defaultValue won’t be much useful when running application because context value will be replaced by the application state data. When testing the component this will be useful with the isolation.

Context.Provider

  
<AppContext.Provider value={/* value to be shared as  state*/} />
  

Provider is the contact point between Context and Component Tree. This works as a data injector. Normally this value to be connected with state of the component. Then that state will be shared through Provider.

Provider will be re-rendered when the value prop is changed. Changes are determined by comparing the new and old values using the same algorithm as Object.is.

Context.Consumer

  
<AppContext.Consumer>
  {value => /* render something based on the context value */}
</AppContext.Consumer>

Consumer is the listener of the Provider. One Provider can have many Consumers. Consumers should be defined as a decedent of the Provider. Otherwise, you will get below error.

TypeError: Cannot destructure property '<your property>' of 'undefined' as it is undefined.

ENOUGH TALK LET'S CODE Memegeneratornet Enough Talk Let's Code ...

It’s coding time!

Context object contains Provider and Consumer. It would be more cleaner to go with separate Provider and Consumer as below.

  
// LocaleContext.js

import { createContext } from 'react';

const LocaleContext = createContext();

export const LocaleProvider = LocaleContext.Provider;
export const LocaleConsumer = LocaleContext.Consumer;
  
// ThemeContext.js

import { createContext } from 'react';

const ThemeContext = createContext();

export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;

We can use above defined Providers inside App component as below.

  
// App.js

import React, { useState } from 'react';
import { LocaleProvider } from './LocaleContext'
import './App.css'
import { ThemeProvider } from './ThemeContext';
import WelcomeMessage from './WelcomeMessage';
import Menu from './Menu';

function App() {

  const [lang, setLang] = useState('en');
  const [theme, setTheme] = useState('light');

  return (
    <LocaleProvider value={{ lang, setLang }}>
      <ThemeProvider value={{ theme, setTheme }}>
        <Menu />
        <WelcomeMessage />
      </ThemeProvider>
    </LocaleProvider>
  );
}

export default App;

I am maintaining two states lang & theme that needs to be shared with component tree. As the value of the Context, I am providing an object with value and setter function of the state.

I have located my consumers in welcome component and MenuItem component (inside Menu component).

  
// WelcomeMessage.js

import React from 'react'
import { LocaleConsumer } from './LocaleContext'
import { ThemeConsumer } from './ThemeContext';
import getLocalizedText from './LanguageService';


export default function WelcomeMessage() {
  return (
    <LocaleConsumer>
      {
        ({ lang }) => (
          <ThemeConsumer>
            {
              ({ theme }) => (
                (
                  <div className={theme}>
                    <h1>{ getLocalizedText(lang, 'WELCOME') }</h1>
                  </div>
                )
              )
            }
          </ThemeConsumer>
        )
      }
    </LocaleConsumer>
  )
}

Welcome component has both consumers to consume the context data. I have used special service to get localized text for given language and key. Also WelcomeComponent is styled to change with the theme.

Did you notice this?
  
({ lang }) => (

Consumer gets the complete object given to the Provider which is defined as below.

   
{ lang, setLang }

Welcome component only need lang variable. Because of that, I have used object destructing.

  
// ThemeChooser.js

import React from 'react'
import { ThemeConsumer } from './ThemeContext'

export default function ThemeChooser() {
  return (
    <ThemeConsumer>
      {
        ({ setTheme }) => (
          <div>
            <span>Active dark theme</span>
            <input type="checkbox" onChange={(e) => setTheme(e.target.checked ? 'dark' : 'light')} />
          </div>
        )
      }
    </ThemeConsumer>
  )
}
  
// LanguageChooser.js

import React from 'react'
import { LocaleConsumer } from './LocaleContext'

export default function LanguageChooser() {

  return (
    <LocaleConsumer>
      {
        ({ setLang}) => (
          (
            <div>
              <select onChange={(e) => setLang(e.target.value)}>
                <option value='en'>EN</option>
                <option value='fr'>FR</option>
                <option value='sw'>SW</option>
              </select>
            </div>
          )
        )
      }
    </LocaleConsumer>
  )
}
  
// Menu.js

import React from 'react'
import LanguageChooser from './LanguageChooser';
import ThemeChooser from './ThemeChooser';
import MenuItem from './MenuItem';

export default function Menu() {
    return (
        <div>
            <MenuItem>
                <LanguageChooser />
            </MenuItem>
            <MenuItem>
                <ThemeChooser />
            </MenuItem>
        </div>
    );
}
// MenuItem.js

import React from 'react'

export default function MenuItem({ children }) {
    return (
        <div>
            {children}
        </div>
    )
}

Here is the final Result.


we made it! meme - Success Kid Original (22723) Page 205 • MemesHappen

Thank you for reading. Let me know your feedback in the comments.

Cheers!

2 Replies to “State Management with React’s Context API”

  1. Aiya whts the difference in using context api and redux. Should we use both at once. Seems both provide the same tjing is it not?

    1. Yes, both provide the same feature of storing the state of an app. But React team suggests using Context API for low-frequency updates such as authenticated user, theme setting and locale. If your application is non-trivial fine to go with Context API. You don’t need any third-party dependency. But Redux comes with some other features such as adding a middleware.

Leave a Reply

Your email address will not be published.