Provider Pattern with React Context API

Provider Pattern with React Context API

Provider Pattern with React Context API 1005 516 Gobinda Thakur

React Provider Pattern

React uses provider pattern in Context API to share data across the tree descendant nodes. It is a powerful concept. You may not find this useful when you are using plain react. But you should consider this pattern while designing a complex app since it solves multiple problems. If you are totally new to this concept, you should check out my blog on React’s Context API.

What do react docs say about context API?

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Meaning it helps us to skip the mandatory hierarchy of passing props for each component in its component tree.

Provider pattern is not only about react context. You might have used a state management library like redux and mobX. I have worked with redux mostly. Here provider is the top most component and it is provided by react-redux. We write it like so:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

const rootElement = document.getElementById('root');
ReactDOM.render(  
   <Provider store={store}>    
     <App />  
   </Provider>,  
   rootElement
);

In fact, react-redux has implemented the provider pattern where Provider component receives the state as props, and post that, each child component has implicit access to the managed state.

Let’s see what problem(s) provider pattern can solve in react. You might have heard about props-drilling. While building your application you may be at a stage where you are drilling through many layers of components.

Props Drilling

Props Drilling

For example, in the image above, we have component <A />. Here we have a prop called “name”. We need this to use in component <I />. For this, we need to send the same props to several layers. If the components <C /> and <G /> don’t need the prop “name” then we shouldn’t expose this data to them. We need to have something so that <I /> can access the data directly without revealing it to all other components.

To solve this problem, we would need a global object to which components can have direct access in the react tree. React’s context API has implemented the provider pattern exactly for this use case. The provider consumer relation is very handy in this context. Provider can provide the data/API which can be consumed by the consumers.

Context API is not the only solution to the above problem. Please go through this link to see how well thought out design can solve such problems.

Where we can use context API?

  • Theming — Pass down app theme
  • i18n — Pass down translation messages
  • Authentication — Pass down current authenticated user

and many more..

Let’s dig into a real example. Here we intend to change the theme and perform translation basis the selected language.

In this application, we have components like <LanguageSelection />, <ThemeContainer /> and <Content />. <LanguageSelection /> has a label “Select Language” and a drop down which has a list of languages. <ThemeContainer /> has a label “Change Theme”, theme type and a toggle button. And the last component is <Content /> which is showing the content “Hello world!!”.

We have three languages – English, French and Spanish. The text on the screen should change to the respective language basis your selection.

There are two themes, light and dark – you can toggle it using the button. It will also show the selected theme.

We can imagine the tree like this:

React Component Tree

Parent component is nothing but the provider which has language and theme in the state. It also has APIs(methods) to change the language and theme. Now all the child components can consume the data and API directly.

via GIPHY

Below is the code for <AppProvider />.

import React, { createContext } from "react";
import { getLocaleCode, getlocaleByCode } from "./data";
import AppContext from "./appContext";

class AppProvider extends React.Component {
 state = {
   localeCodes: [],
   localeObj: null,
   theme: "light"
 };

 updateLocalCode = async e => {
 try {
   const localeObj = await getlocaleByCode(e.target.value);
   this.setState({ localeObj });
 } catch (err) {
   console.log(err);
 }
};

updateTheme = e => {
  this.setState({ theme: e.target.checked ? "dark" : "light" });
};

render() {
  return (
    <AppContext.Provider
       value={{
         state: this.state,
         updateLocale: this.updateLocalCode,
         updateTheme: this.updateTheme
       }}
     >
     <div className={this.state.theme}>{this.props.children}</div
    </AppContext.Provider>
 );
}

componentDidMount = async () => {
 try {
   const localeCodes = await getLocaleCode();
   const localeObj = await getlocaleByCode();
   this.setState({ localeCodes, localeObj });
  } catch (err) {
     console.log(err);
  }
};
}
export default AppProvider;

As you can see, in this component, we are exposing the states, “updateLocale” and “updateTheme”. In the states, we have all data which is shown in the UI whereas “updateLocale” and “updateTheme” are the callbacks which are going to be used by the consumers. Here “updateLocale” holds the definition of updateLocaleCode method – its job is to change the language. “updateTheme” holds the definition of the method “updateTheme” which toggles the theme to light or dark.

We can check the render of the all three components:

LanguageSelection

render() {
 return (
   <AppContext.Consumer>
     {context => (
       <div className="haveMargin">
         <label className="labels">
           {context.state.localeObj.languageLabel}
         </label>
         <select
           value={context.state.localeObj.locale}
           onChange={context.updateLocale}
          >
             <option value="en-US">English</option>
             <option value="fr-FR">French</option>
             <option value="es-ES">Spanish</option>
           </select>
       </div>
    )}
    </AppContext.Consumer>
 );
}

We are using render props and accessing the data like {context.state.localObj.languageLabel}. Also, on change the selection we invoke the callback like {context.updateLocale}.

For an in-depth introduction, you can refer my blog on render props.

Similarly,

ThemeContainer

render() {
 return (
   <AppContext.Consumer>
     {context => (
       <div className="haveMargin">
         <label className="labels">
           {context.state.localeObj.themeLabel}:
         </label>
         ............
         <label className="switch">
          <input type="checkbox" onChange={context.updateTheme} /
          <span className="slider round" />
         </label>
       </div>
     )}
   </AppContext.Consumer>
 );
}

And finally,

Content

render() {
 return (
   <AppContext.Consumer>
     {context => (
       <h2>{context.state.localeObj.content}</h2>
     )}
   </AppContext.Consumer>
 );
}

We access data in ThemeContainer and Content while we invoke the callback {context.updateTheme} basis the toggle button in ThemeContainer.

Conclusion

In summary, you should definitely consider the powerful provider pattern while designing your application. It will surely help in creating a clean design and make you write robust code.

Watch out this space for more articles on React Patterns!

P.S: In case you missed reading our in-depth introduction to Render Props, you can find it here.

Gobinda Thakur

Fullstack web developer

2 Comments
  • Gobinda thanks for a good and crisp understanding of Provider pattern and now Context API implements it… A very nice read..

    • Thank you Siddharth Kar. Please share it with your friends and colleagues. It will help them in understanding the concept.