5.1 React Context

Sometimes there is application state that you need to share with many unrelated components, often deeply nested in your component tree. This is often referred to as "global state".

We could pass this state from component to component as props, but that is both tedious and error prone. Passing state via props through one or more components that don't actually use them, but just pass them on to the next component, is called prop-drilling.

The React Context API provides another way to solve this.

Context lets us pass a value deep into the component tree without explicitly threading it through every component.

Read this first

The official React documentation warns that while the Context API is a good solution in many cases, there are alternatives that should be considered first. In particular, component composition.

# Create a Context object

There are two parts to the Context API: the provider, and the consumer. The provider is a special React component that hold any valid JavaScript data type and announces changes to that value. The consumers subscribe to updates from the provider and receive the value to use like props.

Creating a new context is simple. Suppose that you want to make the current logged-in user object available throughout your component tree.

const UserContext = React.createContext()

Notice the capitalization

Remember the Context object returned from React.createContext() is a React component, and should be written with Pascal case.

The UserContext object has a provider property that holds a JSX component that you can use to make the context value available in the component tree.

function App () {
  const [user, setUser] = useState()

  return (
    <div class='App'>
      <UserContext.Provider value={user}>
        <Toolbar />
        <MainContent />
      </UserContext.Provider>
    </div>
  )
}

# Use the Context value

Read this second

Consuming the Context provider is now simpler with the useContext() hook.

In any component that has the context provider as an ancestor component, you can now easily access the value with the useContext() hook.

function Toolbar () {
  return (
    <header>
      {/* other stuff */}
      <UserMenu />
    </header>
  )
}

function UserMenu () {
  const user = useContext(UserContext)
  return (
    <div>
      <span>{user.firstName}</span>
      {/* other stuff */}
    </div>
  )
}

# Wrap it up in a custom hook module

It is often useful to encapsulate the context functionality in a custom hook module.

/** @module UserContext */

import React from 'react'

const UserContext = React.createContext()

function UserProvider (props) {
  const [user, setUser] = React.useState()
  function logout () {
    setUser(null)
  }
  return <UserContext.Provider value={[user, logout]} {...props} />
}

function useUser () {
  const context = React.useContext(UserContext)
  if (!context) throw new Error('useUser must be used within a UserProvider')
  return context
}

export { UserProvider, useUser }
/** @module App */

import { UserProvider } from './UserContext'

function App () {
  return (
    <div class='App'>
      <UserProvider>
        <Toolbar />
        <MainContent />
      </UserProvider>
    </div>
  )
}
/** @module UserMenu */

import { useUser } from './UserContext'

function UserMenu () {
  const [user, logout] = useUser()
  return (
    <div>
      <span>{user.firstName}</span>
      <span onClick={logout}>Logout</span>
    </div>
  )
}
Last Updated: : 10/14/2020, 12:57:17 PM