Sharing State with the React Context API

John Au-Yeung - Jan 20 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

React components can only pass data from parent to child via props. The Context API adds to that by allowing components with other relationships to share data.

In this article, we’ll look at how to use it to share data between components.

When to Use Context

We should use Context to share data between React components. However, it should be used sparingly since it creates tight coupling between components.

To use it within a simple app, we can write the following:

const ColorContext = React.createContext("green");

class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {value => (  
            <button style={{ color: value }}>{this.props.children}</button>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Provider value="blue">  
          <Button>Click Me</Button>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we created a Context to share data by writing:

const ColorContext = React.createContext("green");
Enter fullscreen mode Exit fullscreen mode

createContext takes a default value as an argument, where we passed in 'green' .

Then in the App component, we have the ColorContext.Provider component with the value prop set to the value that we want to share.

In the example above, it’ll be 'blue' . We wrapped it around the components that we want to share the data with so that we can access the value from that component.

In this case, we created a new Button component, which has the ColorContext.Consumer component. Inside it, we can get the value shared from the context provider from the value parameter in the function we inserted inside the ColorContext.Consumer component.

value should be set to 'blue' since that’s what we set as the value of the value prop.

Inside the function we passed in the consumer, we returned a buttom element with the style prop and we set the color style to value , which is 'blue' .

Alternatives to Context

If we want to pass data into a deeply nested component, we can instead pass in the whole component down to where we want it. This way, we don’t have to worry about passing props to multiple levels to pass something that’s only needed by deeply nested components.

For example, if we want to pass the color prop to Button components, which is contained in a ButtonBar . We can do that as follows:

class Button extends React.Component {  
  render() {  
    return (  
      <button style={{ color: this.props.color }}>{this.props.children}</button>  
    );  
  }  
}

class ButtonBar extends React.Component {  
  render() {  
    return this.props.buttons;  
  }  
}

class App extends React.Component {  
  render() {  
    const buttons = [  
      <Button color="blue">Click Me</Button>,  
      <Button color="green">Click Me 2</Button>  
    ];  
    return <ButtonBar buttons={buttons} />;  
  }  
}
Enter fullscreen mode Exit fullscreen mode

In the App component, we have the Button components in the buttons array. Then we passed the whole array straight down to the ButtonBar component.

Then ButtonBar just returns what we passed in, which is this.props.buttons .

This also means more complexity in the higher-order components, so it may not be suitable in all cases.

Updating Context from a Nested Component

We can pass in functions to the object that we pass into createContext so that we can call them inside the component that has the context consumer component.

For example, we can write the following:

const colorObj = {  
  color: "green",  
  toggleColor: () => {}  
};

const ColorContext = React.createContext(colorObj);  
class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {({ color, toggleColor }) => (  
            <button onClick={toggleColor} style={{ color }}>  
              {this.props.children}  
            </button>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      color: "blue",  
      toggleColor: () => {  
        this.setState(state => ({  
          color: state.color === "green" ? "blue" : "green"  
        }));  
      }  
    };  
  } 

  render() {  
    return (  
      <div>  
        <ColorContext.Provider value={this.state}>  
          <Button>Click Me</Button>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}
Enter fullscreen mode Exit fullscreen mode

The code above starts with defining the colorObj object, which is passed into createContext as the default value of ColorContext .

Then in the App component, we initialize this.state by setting it to an object with the toggleColor function, and the color property set to 'blue' .

We pass this.state as the value of the value prop of ColorContext.Provider .

Then we access the whole object inside the ColorContext.Consumer component in the Button component.

Inside there, we get the color and toggleColor property from the this.state which we passed in from the ColorContext.Provider . Then we pass toggleColor into the onClick prop, and color into the object that we passed into the style prop.

Then when we click the Click Me button, the text color will toggle between blue and green.

Consuming Multiple Contexts

We can consume multiple contexts by nesting them. For example, we can do that as follows:

const ColorContext = React.createContext("green");  
const BorderContext = React.createContext("");
class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {color => (  
            <BorderContext.Consumer>  
              {border => (  
                <button style={{ color, border }}>{this.props.children}</button>  
              )}  
            </BorderContext.Consumer>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Provider value="blue">  
          <BorderContext.Provider value="3px solid green">  
            <Button>Click Me</Button>  
          </BorderContext.Provider>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we create 2 Contexts, ColorContext and BorderContext and passed in values to the value prop to both. We nested the providers in the App component, which means that both contexts can be consumed by the Button component inside.

Then in the Button component, we have consumers for both contexts nested in each other. And then we can get both values that were passed in from the providers.

We then use both values to set the styles of the button .

In the end, we a button with blue text and a thick green border.

Conclusion

We can use the React Context API to share data between components.

It works by creating a Context object with React.createContext . Then we wrap the context provider component outside the components that we want to consume the context from.

Then in the component that we put inside the provider, we have the context consumer component wrapped outside of whatever we want to apply the context value to.

Finally, we can get the value inside the function that we pass inside the Context consumer.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .