React Context, All in One
Tags: react, javascript, context
Published: 2021-08-12 02:58:38

All you need to know about the React Context API: basics, optimization, good practices, testing, and future. All the pieces together. All in One.

## What is React Context for?

✔️ Simple dependency injection mechanism, avoiding the infamous *prop drilling*.
✔️ No third-party libraries, React Context is integrated with React and for sure this API will be updated in the future with many improvements.
✔️ Ideal when you can split your states in order to make them accesible to your React component tree (e.g. Theme, Authentication, i18n, ...)
❌ It is not a *global state management* tool. You manage your state via `useState` or `useReducer`.
❌ If you app state is frequently updated Context is not the best solution.
❌ Not suitable if you need complex capabilities such as side effects, persistence and data serialization.
❌ Worse debugging since you don't have "Redux DevTools" including the actions history for example.
❌ You have to implement it right in order to avoid optimization leaks. React does not help you there. This post does.

## React Context usage example
Let's start straight with some code in order to know:
1. How to create a Context.
2. How to create a Provider which will provide the context value.
3. How to create Consumer components which will use the context value.

```JSX
// index.jsx
ReactDOM.render(
  <MyProvider>
    <MyEntireApp/>
  </MyProvider>,
  document.getElementById('root'),
)
```
```javascript
// myContext.js
import { createContext } from 'react'

// Creating the Context
const MyContext = createContext()

export default MyContext
```
```JSX
// MyProvider.jsx
const MyProvider = ({ children }) => {
  const [state, setState] = useState({})

  const fetch = async () => {
    // Fetching some data
    setState({ ... })
 }

  useEffect(() => {
    fetch()
  }, [])

  // Providing a value
  return (
     <MyContext.Provider value={{state, setState}}>
       {children}
     </MyContext.Provider>
  )
}
```
```JSX
// FunctionalComponent.jsx
const Consumer = () => {
  // Consuming the Context
  const myContext = useContext(MyContext)
  
  return (
    // Here we can access to the context state
  )
}
```
```JSX
// ClassComponent.jsx
class Consumer {
  constructor () { ... }

  render () {
    // Consuming the Context
    <MyContext.Consumer>
      {(myContext) => (
        // Here we can access to the context state
      )}
    </MyContext.Consumer>
  }
}
```
> ⚠️ **When the nearest `<MyContext.Provider>` above the component updates, `React.useContext(...)` will trigger a rerender with the latest context value passed to that MyContext provider**. Even if an ancestor uses `React.memo` or `shouldComponentUpdate`, a rerender will still happen starting at the component itself using `useContext`.

> A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.

https://reactjs.org/docs/hooks-reference.html#usecontext

## What happens with the initial value passed to `React.createContext(...)`?

In our example above we are passing `undefined` as a our initial context value, but at the same time we are overriding it in our Provider:
```javascript
const MyContext = createContext()
```
```JSX
<MyContext.Provider value={{state, setState}}>
  {children}
</MyContext.Provider>
```
The value that `createContext` is receiving as default (*undefined*) will be the one a Consumer will receive if it does not have any Provider above itself in the component tree.

```JSX
const Root = () => {
  // ⚠️ Here we will get an error since we cannot
  // destructure `state` from `undefined`.
  const { state } = useContext(MyContext)
  return <div>{state}</div>
}
ReactDOM.render(<Root />, document.getElementById('root'))
```

In our case, our Consumers will always have a Provider above them, since our Provider wraps the entire application (see `index.js`). The implementation of a custom hook to use our Context could be a cool idea in order to improve code legibility, abstract the use of `useContext`, and throw an error if our Context is used incorrectly (remember, *failing fast*).

```JSX
// MyProvider.jsx
const MyProvider = ({ children }) => {
  const [state, setState] = useState([])

  // Provider stuff...

  <MyContext.Provider value={{state, setState}}>
    {children}
  </MyContext.Provider>
}

// For Hooks
const useMyCtx = () => {
  const context = useContext(MyContext)
  if (context === undefined) {
    throw new Error('useMyCtx must be used withing a Provider')
  }
  return context
}

// For Classes
const ContextConsumer = ({ children }) => {
  return (
    <MyContext.Consumer>
      {context => {
        if (context === undefined) {
          throw new Error('ContextConsumer must be used 
            within a Provider')
        }
        return children(context)
      }}
    </MyContext.Consumer>
  )
}

export { MyProvider, useMyCtx, ContextConsumer }
```
**With Hooks**
```JSX
// FunctionalComponent.jsx
const Consumer = () => {
  const context = useMyCtx()
}
```

**With Classes**
```JSX
// ClassComponent.jsx
class Consumer extends Component {
  constructor() { ... }

  render() {
    return <ContextConsumer>
      {context => // Here we can access to the context state }
      </ContextConsumer>
  }
}
```

## Does my entire app re-render if the Provider state changes?

Depends on how you implemented your provider:

```JSX
// ❌ Bad
// When the provider's state changes, React translates the rendering
// of <MyEntireApp/> as follows:
// React.creatElement(MyEntireApp, ...),
// rendering it as a new reference.
// ⚠️ No two values of the provider’s children will ever be equal,
// so the children will be re-rendered on each state change.
const Root = () => {
  const [state, setState] = useState()

  <MyContext.Provider value={{state, setState}>
    <MyEntireApp />
  </MyContext.Provider>
}
```

```JSX
// ✔️ Good
// When the provider's state changes, the children prop
// stays the same so <MyEntireApp/> is not re-rendering.
// `children` prop share reference equality with its previous
// `children` prop.
const MyProvider = ({ children }) => {
  const [state, setState] = useState()

  <MyContext.Provider value={{state, setState}}>
    {children}
  </MyContext.Provider>
}

const Root = () => {
  <MyProvider>
    <MyEntireApp />
  </MyProvider>
}
```
## Can I store my global state in just one Context?

No. Well, yes, but you shouldn't. The reason is simple, consider the following global state:
```JSON
{
  auth: {...}
  translations: {...}
  theme: {...}
}
```
⚠️ **If a component only consumes the `theme`, it still will be re-rendered even if another state property changes.**

```JSX
// FunctionalComponent.jsx
// This component will be re-rendered when `MyContext`'s
// value changes, even if it is not the `theme`.
const Consumer = () => {
  const { theme } = useContext(MyContext)

  render <ExpensiveTree theme={theme} />
}
```

You should instead split that state in some Contexts. Something like this:
```JSX
// index.jsx
// ❌ Bad
ReactDOM.render(
  <GlobalProvider>
     <MyEntireApp/>
  </GlobalProvider>,
  document.getElementById('root'),
)

// ✔️ Good
ReactDOM.render(
  <AuthProvider>
    <TranslationsProvider>
      <ThemeProvider>
        <MyEntireApp/>
      </ThemeProvider>
    </TranslationsProvider>
  </AuthProvider>,
  document.getElementById('root'),
)
```
As you can see this can end in an endless arrowhead component, so a good practice could be splitting this in two files:
```JSX
// ProvidersWrapper.jsx
// This `ProvidersWrapper.jsx` can help you implementing testing 
// at the same time.
const ProvidersWrapper = ({ children }) => (
  <AuthProvider>
    <TranslationsProvider>
      <ThemeProvider>
        {children}
      </ThemeProvider>
    </TranslationsProvider>
  </AuthProvider>
)
```

```JSX
// index.jsx
ReactDOM.render(
  <ProvidersWrapper>
    <MyEntireApp/>
  </ProvidersWrapper>,
  document.getElementById('root'),
)
```

By doing this, each Consumer should use just what it needs.

### Alternatives to splitting Contexts

Instead of splitting contexts, we could apply the following techniques in order to `<ExpensiveTree />` don't re-render if a property he is not consuming changes:

#### 1. Spliting the Consumer in two with `memo` in between.

```JSX
// FunctionalComponent.jsx
const Consumer = () => {
  const { theme } = useContext(MyContext)
  
  return <ThemeConsumer theme={theme} />
}

const ThemeConsumer = memo(({ theme }) => {
  // The rest of your rendering logic
  return <ExpensiveTree theme={theme} />
})
```

An advanced implmentation would be the creation of a [HOC](https://es.reactjs.org/docs/higher-order-components.html) with a custom `connect(...)` function as follows:
```JSX
const connect = (MyComponent, select) => {
  return function (props) {
    const selectors = select();
    return <WrappedComponent {...selectors} {...props}/>
  }
}
```
```JSX
import connect from 'path/to/connect'

const MyComponent = React.memo(({
    somePropFromContext,
    otherPropFromContext, 
    someRegularPropNotFromContext  
}) => {
    ... // regular component logic
    return(
        ... // regular component return
    )
});

const select = () => {
  const { someSelector, otherSelector } = useContext(MyContext);
  return {
    somePropFromContext: someSelector,
    otherPropFromContext: otherSelector,
  }
}

export default connect(MyComponent, select)
```

Source: https://github.com/reactjs/rfcs/pull/119#issuecomment-547608494

However this is against the nature of React Context and does not solve the main issue: the HOC what wraps the Component still tries to re-render, there may be multiple HOCs for just one updated resulting in an *expensive* operation.

#### 2. One component with `useMemo` inside
```JSX
const Consumer = () => {
  const { theme } = useContext(MyContext)
  
  return useMemo(() => {
    // The rest of your rendering logic
    return <ExpensiveTree theme={theme} />
  }, [theme])
}
```
#### 3. Third-party React Tracked

> Prior to v1.6.0, React Tracked is a library to replace React Context use cases for global state. React hook useContext triggers re-renders whenever a small part of state object is changed, and it would cause performance issues pretty easily. React Tracked provides an API that is very similar to useContext-style global state.

```JSX
const useValue = () => useState({
  count: 0,
  text: 'hello',
})

const { Provider, useTracked } = createContainer(useValue)

const Consumer = () => {
  const [state, setState] = useTracked()
  const increment = () => {
    setState((prev) => ({
      ...prev,
      count: prev.count + 1,
    })
  }
  return (
    <div>
      <span>Count: {state.count}</span>
      <button type="button" onClick={increment}>+1</button>
    </div>
  )
}
```
> The useTracked hook returns a tuple that useValue returns, except that the first is the state wrapped by proxies and the second part is a wrapped function for a reason.

> Thanks to proxies, the property access in render is tracked and this component will re-render only if state.count is changed.

https://github.com/dai-shi/react-tracked

## Do I need to memoize my Provider value or my component?

It depends. Apart from the cases we just saw... Do you have a parent above your Provider which can be updated forcing a natural children re-rendering by React?

```JSX
// ⚠️ If Parent can be updated (via setState() or even via
// a grandparent) we must be careful since everything
// will be re-rendered.
const Parent = () => {
  const [state, setState] = useState()

  // Stuff that forces a re-rendering...

  return (
    <Parent>
      <MyProvider>
        <MyEntireApp/>
      </MyProvider>
    </Parent>
  )
}
```
If so, yes. You will have to memoize both the Provider **and** your Component as follows:

```JSX
// MyProvider.jsx
const MyProvider = ({ children }) => {
  const [state, setState] = useState({})

  // With `useMemo` we avoid the creation of a new object reference
  const value = useMemo(
    () => ({
      state,
      setState,
    }),
    [state]
  )

  <MyContext.Provider value={value}>
    {children}
  </MyContext.Provider>
}
```
```JSX
// FunctionalComponent.jsx
// With `memo` we avoid the re-rendering if props didn't change
// Context value didn't change neither thanks to the previous 
// `useMemo`.
const Consumer = memo((props) => {
  const myContext = useContext(MyContext)
})
```

But this is unlikely, you want always to wrap your entire application with your Providers as we saw previously.

```JSX
ReactDOM.render(
  <MyProvider>
    <MyEntireApp/>
  </MyProvider>,
  document.getElementById('root'),
)
```

## Splitting Context in two: `stateContext` and `setStateContext`

For the same reasons we already talked about previously:

⚠️ **A Consumer that just changes the state of a Context (by using `setState` or `dispatch`) will be re-rendered once the update is performed and the value changes.**

That's why it is a good idea to split that context in two as follows:
```javascript
const CountStateContext = createContext()
const CountUpdaterContext = createContext()
```

```JSX
const Provider = () => {
  const [count, setCount] = usetState(0)

  // We memoize the setCount in order to do not create a new
  // reference once `count` changes. An alternative would be
  // passing directly the setCount function (without 
  // implementation) via the provider's value or implementing its 
  // behaviour in our custom hook.
  const memoSetCount = useCallback(() => setCount((c) => c + 1), [
    setCount,
  ])

  return (
    <CountStateContext.Provider value={count}>
      <CountUpdaterContext.Provider value={memoSetCount}>
        {props.children}
      </CountUpdaterContext.Provider>
    </CountStateContext.Provider>
  )
}

const useCountState() {
  const countStateCtx = useContext(StateContext)
  if (typeof countStateCtx === 'undefined') {
    throw new Error('useCountState must be used within a Provider')
  }
  return countStateCtx 
}

function useCountUpdater() {
  const countUpdaterCtx = useContext(CountUpdaterContext)
  if (typeof countUpdaterCtx === 'undefined') {
    throw new Error('useCountUpdater must be used within a Provider')
  }
  // We could here implement setCount to avoid the previous useCallback
  // const setCount = () => countUpdaterCtx((c) => c + 1)
  // return setCount
  return countUpdaterCtx
}
```
```JSX
// CountConsumer.jsx
// This component will re-render if count changes.
const CountDisplay = () => {
  const count = useCountState()

  return (
    <>
      {`The current count is ${count}. `}
    </>
  )
})
```
```JSX
// CountDispatcher.jsx
// This component will not re-render if count changes.
const CounterDispatcher = () => {
  const countUpdater = useCountUpdater()

  return (
    <button onClick={countUpdater}>Increment count</button>
  )
}
```

Components that use both the state and the updater will have to import them like this:
```JSX
const state = useCountState()
const dispatch = useCountDispatch()
```
You can export both of them in a single function `useCount` doing this:
```JSX
const useCount = () => {
  return [useCountState(), useCountDispatch()]
}
```

## What about using `useReducer`? Do I need to take in count everything we talked about?

Yes, of course. The unique difference about using the `useReducer` hook is that now you are not using `setState` in order to handle the state. 

⚠️ **Remember, React Context does not manage the state, you do it via `useState` or `useReducer`.**

The possible optimization leaks remains the same we talked about this article.

## React Context vs Redux

Let me link you an awesome article for this, authored by Mark "acemarke" Erikson, Redux mantainer:

> **Are Context and Redux the same thing?**
No. They are different tools that do different things, and you use them for different purposes.

> **Is Context a "state management" tool?**
No. Context is a form of Dependency Injection. It is a transport mechanism - it doesn't "manage" anything. Any "state management" is done by you and your own code, typically via useState/useReducer.

> **Are Context and useReducer a replacement for Redux?**
No. They have some similarities and overlap, but there are major differences in their capabilities.

> **When should I use Context?**
Any time you have some value that you want to make accessible to a portion of your React component tree, without passing that value down as props through each level of components.

> **When should I use Context and useReducer?**
When you have moderately complex React component state management needs within a specific section of your application.

> **When should I use Redux instead?**
Redux is most useful in cases when:
* You have larger amounts of application state that are needed in many places in the app.
* The app state is updated frequently over time.
* The logic to update that state may be complex.
* The app has a medium or large-sized codebase, and might be worked on by many people.
* You want to be able to understand when, why, and how the state in your application has updated, and visualize the changes to your state over time.
* You need more powerful capabilities for managing side effects, persistence, and data serialization.

https://blog.isquaredsoftware.com/2021/01/context-redux-differences/#context-and-usereducer

## Testing

Let's test the following case: we have a Provider which fetchs asynchronously some Articles in order to make them available to our fellow Consumers.

We will work with the following mock:
```JSON
[
  {
    "id": 1,
    "title": "Article1",
    "description": "Description1"
  },
  {
    "id": 2,
    "title": "Article2",
    "description": "Description2"
  }
]
```

```JSX
// ArticlesProvider.jsx
const ArticlesProvider = ({ children }) => {
  const [articles, setArticles] = useState([])

  const fetchArticles = async () => {
    const articles = await ArticlesService.get('/api/articles')

    setArticles(articles)
  }

  useEffect(() => {
    fetchArticles()
  }, [])

  return (
    <ArticlesContext.Provider value={{ articles, setArticles }}>
      {children}
    </ArticlesContext.Provider>
  )
}

const useArticles = () => {
  const articlesCtx = useContext(ArticlesContext)
  if (typeof articlesCtx === "undefined") {
    throw new Error("articlesCtx must be used within a Provider")
  }
  return articlesCtx
}

export { ArticlesProvider, useArticles }
```
```JSX
// ArticlesProvider.spec.jsx
describe("ArticlesProvider", () => {
  const noContextAvailable = "No context available."
  const contextAvailable = "Articles context available."

  const articlesPromise = new Promise((resolve) => resolve(articlesMock))
  ArticlesService.get = jest.fn(() => articlesPromise)

  // ❌ This code fragment is extracted directly from Testing Library
  // documentation but I don't really like it, since here we are
  // testing the `<ArticlesContext.Provider>` functionality, not
  // our `ArticlesProvider`.
  const renderWithProvider = (ui, { providerProps, ...renderOptions }) => {
    return render(
      <ArticlesContext.Provider {...providerProps}>
        {ui}
      </ArticlesContext.Provider>,
      renderOptions
    )
  }

  // ✔️ Now we are good to go, we test what our Consumers will actually use.
  const renderWithProvider = (ui, { ...renderOptions }) => {
    return render(<ArticlesProvider>{ui}</ArticlesProvider>, renderOptions)
  }

  // ⚠️ We mock a Consumer in order to test our Provider.
  const ArticlesComsumerMock = (
    <ArticlesContext.Consumer>
      {(articlesCtx) => articlesCtx ? (
          articlesCtx.articles.length > 0 &&
            articlesCtx.setArticles instanceof Function && (
              <span>{contextAvailable}</span>
            )
        ) : (
          <span>{noContextAvailable}</span>
        )
      }
    </ArticlesContext.Consumer>
  )

  it("should no render any articles if no provider is found", () => {
    render(ArticlesComsumerMock)

    expect(screen.getByText(noContextAvailable)).toBeInTheDocument()
  })

  it("should render the articles are available", async () => {
    renderWithProvider(ArticlesComsumerMock)

    await waitFor(() => {
      expect(screen.getByText(contextAvailable)).toBeInTheDocument()
    })
  })
})

```

Time to test our Consumer:
```JSX
// Articles.jsx
const Articles = () => {
  const { articles } = useArticles()

  return (
    <>
      <h2>List of Articles</h2>
      {articles.map((article) => (
        <p>{article.title}</p>
      ))}
    </>
  )
}
```
```JSX
// Articles.spec.jsx
describe("Articles", () => {
  const articlesPromise = new Promise((resolve) => resolve(articlesMock))
  ArticlesService.get = jest.fn(() => articlesPromise)

  const renderWithProvider = (ui, { ...renderOptions }) => {
    return render(<ArticlesProvider>{ui}</ArticlesProvider>, renderOptions)
  }

  it("should render the articles list", async () => {
    renderWithProvider(<Articles />)

    await waitFor(() => {
      expect(screen.getByText("List of Articles")).toBeInTheDocument()
    })

    articlesMock.forEach((article) => {
      expect(screen.getByText(article.title)).toBeInTheDocument()
    })
  })
})
```

## Unstable feature: observed bits

```javascript
// react/index.d.ts
function useContext<T>(context: Context<T>/*, (not public API) observedBits?: number|boolean */): T;
```
`observedBits` is a hidden experimental feature that represent what context values did change.

We can prevent unnecessary re-renders in a Global state by calculating what bits changed and telling our components to observe the bits we are *using*.

```JSX
// globalContext.js
import { createContext } from 'react';

const store = {
  // The bit we want to observe
  observedBits: {
    theme: 0b001,
    authentified: 0b010,
    translations: 0b100
  },
  initialState: {
    theme: 'dark',
    authentified: false,
    translations: {}
  }
};

const getChangedBits = (prev, next) => {
  let result = 0;

  // ⚠️ With `result OR bits[key]` we calculate the total bits
  // that changed, if only `theme` changed we will get 0b001,
  // if the three values changed we will get: 0b111.
  Object.entries(prev.state).forEach(([key, value]) => {
    if (value !== next.state[key]) {
      result = result | store.observedBits[key];
    }
  });
  return result;
};

const GlobalContext = createContext(undefined, getChangedBits);

export { GlobalContext, store };
```
```JSX
// Theme.jsx
const Theme = () => {
  console.log('Re-render <Theme />');
  // ⚠️ No matter if the state changes, this component will only
  // re-render if the theme is updated
  const { state } = useContext(GlobalContext, store.observedBits.theme);

  return <p>Current theme: {state.theme}</p>;
};
```
Keep in mind this is an unstable feature, you are limited to observe 30 values ([MaxInt.js] (https://github.com/facebook/react/blob/main/packages/react-reconciler/src/MaxInts.js)) and you will be warned in console :P. I would prefer splitting contexts to pass the necessary props to your application tree, following the initial nature of React Context, while waiting for updates.

A complete demo with a functional playground of this can be found here: https://stackblitz.com/edit/react-jtb3lv


## The Future

There are already some proposals to implement the `selector` concept, in order to let React manage these optimizations if we are just observing one value in a *global* state:

```javascript
const context = useContextSelector(Context, c => c.selectedField)
```
https://github.com/facebook/react/pull/20646

## Bibliography

Interesting articles/comments I have been reading so far that helped me to put all the pieces together, including some stackblitz to play with the re-renders:

* [Avoiding unnecessary renders with React context](https://frontarm.com/james-k-nelson/react-context-performance/) - James K Nelson
* [useMemo inside Context API - React](https://blog.agney.dev/useMemo-inside-context/) - Agney Menon
* [4 options to prevent extra rerenders with React context](https://blog.axlight.com/posts/4-options-to-prevent-extra-rerenders-with-react-context/) - Daishi Kato
* [How to use React Context effectively](https://kentcdodds.com/blog/how-to-use-react-context-effectively) - Kent C. Dodds
* [How to optimize your context value](https://kentcdodds.com/blog/how-to-optimize-your-context-value) - Kent C. Dodds
* [React Context: a Hidden Power](https://dev.to/alexkhismatulin/react-context-a-hidden-power-3h8j) - Alex Khismatulin
* [Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)](https://blog.isquaredsoftware.com/2021/01/context-redux-differences/#context-and-usereducer) - Mark Erikson
* [Preventing rerenders with React.memo and useContext hook](https://github.com/facebook/react/issues/15156) - Dan Abramov
* [RFC: Context selectors](https://github.com/reactjs/rfcs/pull/119) - Pedro Bern

## Key Points

* When the nearest Provider above the component is updated, this component will trigger a re-render even if an ancestor uses `React.memo` or `shouldComponentUpdate`.
* The value that `React.createContext(...)` is receiving as default will be the one a Consumer will receive if it does not have any Provider above itself in the component tree.
* In order to avoid the re-rendering of the entire app (or the use of `memo`), the Provider must receive `children` as a prop to keep the references equal.
* If you implement a Global Provider, no matter what property will be update a Consumer will always trigger a re-render.
* If Parent can be updated (via setState() or even via
a grandparent) we must be careful since everything
will be re-rendered. We will have to memo both the Provider and the Consumers.
* A Consumer that just changes the state of a Context (by using `setState` or `dispatch`) will be re-rendered once the update is performed and the value changes, so it is recommend to split that Context in two: StateContext and DispatchContext.
* Remember, React Context does not manage the state, you do it via `useState` or `useReducer`.
* Implement a custom mock in order to properly test your Provider, `<Context.Provider {...props} />` is not what your components will directly consume.
* `observedBits` is an hidden experimental feature which can helps us to implement a global state avoiding unnecessary re-renders.

That was it, hope you like it!