React context and state hook are two new concept of the React API. This post show you how to mesh the two concepts together, so you can consume parent context in functional components.

Context API uses a provider/consumer pattern, which allow top level component to pass data to children. State hook let you augment functional (stateless) components and make them stateful. Here is a quick example of state hook from React docs. We will use this as the basis for context integration.

import { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Simple Context

We will augment the example above with following changes,

  • Use createContext to define a context instance, CountCtx
  • Refactor the count display line into a child component, Count.js
  • Wrap the children with CountCtx.Provider
import React, { useState, createContext } from "react"
import Counter from "./Counter"

// Create context for count, default to 0
export const CountCtx = createContext(0)

function Example() {
    const [count, setCount] = useState(0)

    return (
        <div>
            <CountCtx.Provider value={count}>
                <Counter />
                <button onClick={() => setCount(count + 1)}>+1</button>
            </CountCtx.Provider>
        </div>
    )
}

export default Example

Note we are binding CountCtx context to state hook with line, <CountCtx.Provider value={count}>.

Next we will create a new Count.js file, which houses the count display line. Here we have two possible paths,

  • Wrap the child component in CountCtx.Consumer
  • Use native hook useContext

useContext hook is much cleaner, since it treats the context just like any other hook.

import React, { useContext } from "react"
import { CountCtx } from "./App"

function Counter() {
    const count = useContext(CountCtx)
    return (
        <div>Counter is {count}</div>
    )
}

export default Counter

Mutating Context from Children

Previous example demonstrated a simple, one way context passing. If you have worked with Redux, or any global state managers, the next natural step is to mutate context from a child node.

The recommend pattern is to pass context mutator along with the context object. State hook also follow the same pattern, where useState returns a tuple of value and mutator. This makes a very natural integration between the two.

import React, { useState, createContext } from 'react';
import Counter from "./Counter"

export const CountCtx = createContext([0, () => {}])

function Example() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <CountCtx.Provider value={[count, setCount]}>
                <Counter />
                <button onClick={() => setCount(count + 1)}>parent button +1</button>
            </CountCtx.Provider>
        </div>
    )
}

export default Example

Note now the context object, CountCtx, is in the tuple form of [0, () => {}], where first element is the default value, and second is the mutator. The context provider, in turn, will pass down both state value and state mutator, <CountCtx.Provider value={[count, setCount]}>.

import React, { useContext } from 'react';
import { CountCtx } from "./App"

function Counter() {
    const [count, setCounter] = useContext(CountCtx)
    return (
        <>
            <div>Counter is {count}</div>
            <button onClick={() => setCounter(count + 5)}>child button +5</button>
        </>
    )
}

export default Counter

The consumer can now pick up both context value and mutator with useContext, with syntax identical to useState. This is what I call a beautiful API.