React's Context API Tutorial: What Context is and How to use it!

React’s Context API has become the state management tool of choice for many, oftentimes replacing Redux altogether. In this quick tutorial, you’ll see an introduction to what Context is and how to use it!

Consider this tree, in which the bottom boxes represent separate components:

Component tree

We can easily add state to the bottom components, but until now the only way to pass data to a component’s sibling was to move state to a higher component and then pass it back down to the sibling via props.

Passing data via props

If we later find out that the sibling of the component with state also needs the data, we have to lift state up again, and pass it back down:

Passing state down through multiple levels

While this solution does work, problems begin if a component on a different branch needs the data:

More distant component requires data

In this case, we need to pass state from the top level of the application through all the intermediary components to the one which needs the data at the bottom, even though the intermediary levels don’t need it. This tedious and time-consuming process is known as prop drilling.

Prop drilling

This is where Context API comes in. It provides a way of passing data through the component tree via a Provider-Consumer pair without having to pass props down through every level. Think of it as the components playing Catch with data - the intermediary components might not even “know” that anything is happening:

Context in action

To demonstrate this, we will create this funky (and super useful) day-to-night switching image.

If you want to see the full code, be sure to check out the Scrimba playground for this article.

Create Context

To begin, we create a new Context. As we want the entire app to have access to this, we go to index.js and wrap the app in ThemeContext.Provider.

We also pass the value prop to our Provider. This holds the data we want to save. For now, we just hardcode in 'Day'.

import React from "react";
import ReactDOM from "react-dom";
import ThemeContext from "./themeContext";

import App from "./App";

ReactDOM.render(
  <ThemeContext.Provider value={"Day"}>
    <App />
  </ThemeContext.Provider>,
  document.getElementById("root")
);

Consuming Context with contextType

Currently, in App.js, we are simply returning the <Image /> component.

import React from "react";
import Image from "./Image";

class App extends React.Component {
  render() {
    return (
      <div className="app">
        <Image />
      </div>
    );
  }
}

export default App;

Our goal is to use Context to switch the classNames in Image.js from Day to Night, depending on which image we want to render. To do this, we add a static property to our component called ContextType and then use string interpolation to add it to the classNames in the <Image /> component.

Now, the classNames contain the string from the value prop. Note: I have moved ThemeContext into its own file to prevent a bug.

import React from "react";
import Button from "./Button";
import ThemeContext from "./themeContext";

class Image extends React.Component {
  render() {
    const theme = this.context;
    return (
      <div className={`${theme}-image image`}>
        <div className={`${theme}-ball ball`} />
        <Button />
      </div>
    );
  }
}

Image.contextType = ThemeContext;

export default Image;

Context.Consumer

Unfortunately, this approach only works with class-based components. If you’ve learned about Hooks in React already, you’ll know we can do just about anything with functional components these days. So for good measure, we should convert our components into functional components and then use ThemeContext.Consumer component to pass info through the app.

This is done by wrapping our elements in an instance of <ThemeContext.Consumer> and within that (where the children go), providing a function which returns the elements. This uses the “render prop” pattern where we provide a regular function as a child that returns some JSX to render.

import React from "react";
import Button from "./Button";
import ThemeContext from "./themeContext";

function Image(props) {
  // We don't need this anymore
  // const theme = this.context

  return (
    <ThemeContext.Consumer>
      {theme => (
        <div className={`${theme}-image image`}>
          <div className={`${theme}-ball ball`} />
          <Button />
        </div>
      )}
    </ThemeContext.Consumer>
  );
}

// We don't need this anymore
// Image.contextType = ThemeContext;

export default Image;

Note: We also need to wrap the <Button /> component in <ThemeContext.Consumer> - this allows us to add functionality to the button later.

import React from "react";
import ThemeContext from "./themeContext";

function Button(props) {
  return (
    <ThemeContext.Consumer>
      {context => (
        <button className="button">
          Switch
          <span role="img" aria-label="sun">
            🌞
          </span>
          <span role="img" aria-label="moon">
            🌚
          </span>
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default Button;

Extract Context Provider

We are currently passing a hard-coded value down through the Provider, however, our goal is to switch between night and day with our button.

This requires moving our Provider to a separate file and putting it in its own component, in this case, called ThemeContextProvider.

import React, { Component } from "react";
const { Provider, Consumer } = React.createContext();

class ThemeContextProvider extends Component {
  render() {
    return <Provider value={"Day"}>{this.props.children}</Provider>;
  }
}

export { ThemeContextProvider, Consumer as ThemeContextConsumer };

Note: the value property is now being handled in the new file ThemeContext.js, and should, therefore, be removed from index.js.

Changing Context
To wire up the button, we first add state to ThemeContextProvider:

import React, { Component } from "react";
const { Provider, Consumer } = React.createContext();

// Note: You could also use hooks to provide state and convert this into a functional component.
class ThemeContextProvider extends Component {
  state = {
    theme: "Day"
  };
  render() {
    return <Provider value={"Day"}>{this.props.children}</Provider>;
  }
}

export { ThemeContextProvider, Consumer as ThemeContextConsumer };

Next, we add a method for switching between day and night:

toggleTheme = () => {
  this.setState(prevState => {
    return {
      theme: prevState.theme === "Day" ? "Night" : "Day"
    };
  });
};

Now we change our value property to this.state.theme so that it returns the info from state.

 render() {
    return <Provider value={this.state.theme}>{this.props.children}</Provider>;
  }
}

Next, we change value to an object containing {theme: this.state.theme, toggleTheme: this.toggleTheme}, and update all the places where we use a single value to look for theme in an object. This means that every theme becomes context and every reference to theme as value becomes context.theme.

Finally, we tell the button to listen for the onClick event and then fire context.toggleTheme - this updates the Consumers which are using the state from the Provider. The code for the button looks like this:

import React from "react";
import { ThemeContextConsumer } from "./themeContext";

function Button(props) {
  return (
    <ThemeContextConsumer>
      {context => (
        <button onClick={context.toggleTheme} className="button">
          Switch
          <span role="img" aria-label="sun">
            🌞
          </span>
          <span role="img" aria-label="moon">
            🌚
          </span>
        </button>
      )}
    </ThemeContextConsumer>
  );
}

export default Button;

Our button now switches the image between night and day in one click!

Context caveats

Like all good things in code, there are some caveats to using Context:

  • Don’t use Context to avoid drilling props down just one or two layers. Context is great for managing state which is needed by large portions of an application. However, prop drilling is faster if you are just passing info down a couple of layers.

  • Avoid using Context to save state that should be kept locally. So if you need to save a user’s form inputs, for example, use local state and not Context.

  • Always wrap the Provider around the lowest possible common parent in the tree - not the app’s highest-level component. No need for overkill.

  • Lastly, if you pass an object as your value prop, monitor performance and refactor as necessary. This probably won’t be needed unless a drop in performance is noticeable.

Wrap up

This example is pretty simple and it would probably be easier to put state in the app and pass it down via props. However, it hopefully shows the power of having Consumers which can access data independently of the components above them in the tree.

Happy coding :)

#react-js #react #react-hooks #javascript #api

React's Context API Tutorial: What Context is and How to use it!
31.60 GEEK