React Context API

The React Context API allows us to share data between components without the need for passing props to each child component. One of the biggest data problems with have in React is how to pass data down an element tree from parent to a child element. There’s several ways to achieve this including Redux and Mobx as well as the slightly more dreaded “prop drilling”.

Using the Context API we can essentially declare components which accept a specific context and in the parent we “provide” that context. This means child components will get context directly.

Let’s see how we implement a context…

We need a context (the data), a context provider and a context consumer.

  • Create a folder names contexts
  • We’ll create a simple string array for our data, so we create our context like this (mine’s in a file named NameContext.tsx
    import { createContext } from "react";
    
    export const NameContext = createContext<string[]>([]);
    

    In a real world application the NameContext file would have the types/interfaces for our application data, but we’re just making a simple example here.

  • Now we can wrap any part of our application in our NameContext, so in App.tsx I’ll have the following
    import { useState } from 'react';
    import './App.css';
    import { NameContextList } from './components/List';
    import { NameContext } from './contexts/NameContext';
    
    function App() {
      const [issues, _] = useState(["One", "Two", "Three"]);
    
      return (
        <NameContext.Provider value={issues}>
          <NameContextList />
        </NameContext.Provider>
      );
    }
    
    export default App;
    
  • How you handle this part is upto you, but I’m going to first create a List component which we can pass props to, then a context consumer – this will allow me to test the actual component without having to create a context provider, so my List.tsx looks like this
    import { NameContext } from "../contexts/NameContext";
    
    
    export const List = ({items}: {items : string[]}) => (
        <ul>
            {items.map(item => {
                return <li>{item}</li>    
            })}
        </ul>
    )
    
    
    export const NameContextList = () => (
        <NameContext.Consumer>
        {(ctx) => (
            <List items={ctx} />
        )}
      </NameContext.Consumer>
    )
    

    We can also use the hook useContext like this

    export const NameContextList = () => {
        const names = useContext(NameContext)
        return <List items={names} />
    }
    

When we run this application, the App.tsx creates some data via useState and supplies this to the provider. Obviously if you were fetching data from a service or API, facade or whatever, then you’ve ofcourse change this slightly, but the concept’s much the same. The context consumer will then receive the data and it’s not been passed down via the props.

Another addition to this might be to wrap the line

const names = useContext(NameContext);

in a method such as

export const useNameContext = () => {
  const context = useContext(NameContext);

  if(!context) {
     throw new Error("NameContext is in an invald state");
  }

  return context;
}

This then just allows us to write code (especially in Typescript) safe in the knowledge the context was created.