Home > Others > An Introduction To React’s Context API

An Introduction To React’s Context API

January 13th, 2020 Leave a comment Go to comments
Smashing Editorial

An Introduction To React’s Context API

An Introduction To React’s Context API

Yusuff Faruq

2020-01-13T11:30:00+00:002020-01-13T11:36:37+00:00

For this tutorial, you should have a fair understanding of hooks. Still, before we begin, I’ll briefly discuss what they are and the hooks we’ll be using in this article.

According to the React Docs:

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.”

That is basically what a React hook is. It allows us to use state, refs and other React features in our functional components.

Let us discuss the two hooks we will encounter in this article.

The useState Hook

The useState hook allows us to use state in our functional components. A useState hook takes the initial value of our state as the only argument, and it returns an array of two elements. The first element is our state variable and the second element is a function in which we can use the update the value of the state variable.

Let’s take a look at the following example:

import React, {useState} from "react";

function SampleComponent(){
   const [count, setCount] = useState(0);
}

Here, count is our state variable and its initial value is 0 while setCount is a function which we can use to update the value of count.

The useContext Hook

I will discuss this later in the article but this hook basically allows us to consume the value of a context. What this actually means will become more apparent later in the article.

Yarn Workspaces

Yarn workspaces let you organize your project codebase using a monolithic repository (monorepo). React is a good example of an open-source project that is monorepo and uses Yarn workspaces to achieve that purpose. Learn more ?

Why Do We Need The Context API?

We want to build a “theme toggler” component which toggles between light mode and dark mode for our React app. Every component has to have access to the current theme mode so they can be styled accordingly.

Normally, we would provide the current theme mode to all the components through props and update the current theme using state:

import React from "react";
import ReactDOM from "react-dom";

function App() {
  return (
    <div>
      <Text theme= "blue" />
      <h1>{theme}</h1>
    </div>
  );
}

function Text({theme}) {
return(
  <h1 style = {{
     color: `${theme}`
  }}>{theme}</h1>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

In the code sample above, we created a Text Component which renders an h1 element. The color of the h1 element depends on the current theme mode. Currently, the theme is blue. We can toggle between blue and red themes by using state.

We will create a state called “theme” using the useState hook. The useState hook will return the current value of the theme and a function which we can use to update the theme.

So, let us create our theme state:

const [theme, setTheme] = React.useState("blue");

We will also add a button element to our App component. This button will be used to toggle the themes and it needs a click event handler. So, let us write the click event handler like so:

const onClickHandler = () => {
  setTheme();
}

Now, we want to set the new theme to Red if the current theme is Blue, and vice versa. Instead of using an if statement, a more convenient way to do this is with the help of the ternary operator in JavaScript.

setTheme( theme === "red"? "blue": "red");

So now, we have written our onClick handler. Let’s add this button element to the App component:

<button onClick = {onClickHandler}>Change theme</button>

Let us also change the value of the theme props of the Text component to the theme state.

<Text theme={theme}/>

Now, we should have this:

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";


function App() {
  const[theme, setTheme] = React.useState("red");

  const onClickHandler = () => {
  setTheme( theme === "red"? "blue": "red");
  }

  return (
    <div>
      <Text theme={theme}/>
      <button onClick = {onClickHandler}>Change theme</button>
    </div>
  );
}

function Text({theme}) {
return(
  <h1 style = {{
     color: `${theme}`
  }}>{theme}</h1>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

We can now toggle between our two themes. However, if this was a much larger application, it would be difficult to use the theme in deeply nested components and the code becomes unwieldy.

Introducing The Context API

Let me introduce the Context API. According to the React documentation:

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

For a more in-depth definition, it provides a way for you to make particular data available to all components throughout the component tree no matter how deeply nested that component may be.

Let us look at this example:

const App = () => {
  return(
    <ParentComponent theme = "light"/>
  );
}

const ParentComponent = (props) => (
  <Child theme = {props.theme} />
)

const Child = (props) => (
  <Grandchild theme = {props.theme} />
)

const Grandchild = (props) => (
  <p>Theme: {props.theme}</p>
)

In the example above, we specified the application theme using a props in the ParentComponent called theme. We had to pass that props to all components down the component tree to get it where it is needed which is the GrandChild component. The ChildComponent had nothing to do with the theme props but was just used as an intermediary.

Now, imagine the GrandChild component was more deeply nested than it was in the top example. We would have to pass the theme props the same way we did here which would be cumbersome. This is the problem that Context solves. With Context, every component in the component tree has access to whatever data we decide to put in our context.

Let’s Get Started With Context

It’s time to replicate the theme toggling button we built at the beginning of the article with the Context API. This time, our theme toggler will be a separate component. We will build a ThemeToggler component which switches the theme of our React app using Context.

First, let us initialize our React app. (I prefer using create-react-app but you can use whatever method you prefer.)

Once you have initialized your React project, create a file called ThemeContext.js in your /src folder. You can also create a folder called /context and place your ThemeContext file in there if you want.

Now, let us move on.

Creating Your Context API

We will create our theme context in our ThemeContext.js file.

To create a context, we use React.createContext which creates a context object. You can pass in anything as an argument to React.createContext. In this case, we are going to pass in a string which is the current theme mode. So now our current theme mode is the “light” theme mode.

import React from "react";

const ThemeContext = React.createContext("light");
export default ThemeContext;

To make this context available to all our React components, we have to use a Provider. What is a Provider? According to the React documentation, every context object comes with a Provider React component that allows consuming components to subscribe to context changes. It is the provider that allows the context to be consumed by other components. That said, let us create our provider.

Go to your App.js file. In order to create our provider, we have to import our ThemeContext.

Once the ThemeContext has been imported, we have to enclose the contents of our App component in ThemeContext.Provider tags and give the ThemeContext.Provider component a props called value which will contain the data we want to make available to our component tree.

function App() {
  const theme = "light";
  return (
    <ThemeContext.Provider value = {themeHook}>
      <div>
      </div>
    </ThemeContext.Provider>
  );
}

So now the value of “light” is available to all our components (which we will write soon).

Creating Our Theme File

Now, we will create our theme file that will contain the different color values for both our light and dark themes. Create a file in your /src folder called Colors.js.

In Colors.js, we will create an object called AppTheme. This object will contain the colors for our themes. Once you are done, export the AppTheme object like so:

const AppTheme = {
    light: {
        textColor: "#000",
        backgroundColor: "#fff"
    },
    dark: {
        textColor: "#fff",
        backgroundColor: "#333"
    }
}

export default AppTheme;

Now it’s time to start creating our different React components.

Creating Our React Components

Let’s create the following components:

  • Header
  • ThemeToggler
  • MainWithClass
Header.jsx
import React from "react";
import ThemeToggler from "./ThemeToggler";

const headerStyles = {
    padding: "1rem",
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center"
}
const Header = () => {
    return(
        <header style = {headerStyles}>
            <h1>Context API</h1>
            <ThemeToggler />
        </header>
    );
}

export default Header;
ThemeToggler.jsx

(For now, we will just return an empty div.)

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

const themeTogglerStyle = {
    cursor: "pointer"
}
const ThemeToggler = () => {
        return(
            <div style = {themeTogglerStyle}>
            </div>
    );
}

export default ThemeToggler;

Consuming Context With Class-Based Components

Here, we will use the value of our ThemeContext. As you may already know, we have two methods of writing components in React: through functions or classes. The process of use context in both methods is different so we will create two components to serve as the main section of our application: MainWithClass and MainWithFunction.

Let us start with MainWithClass.

MainWithClass.jsx

We will have to import our ThemeContext and AppTheme. Once that is done, we will write a class that returns our JSX from a render method. Now we have to consume our context. There are two methods to do this with class-based components:

  1. The first method is through Class.contextType.

    To use this method, we assign the context object from our ThemeContext to contextType property of our class. After that, we will be able to access the context value using this.context. You can also reference this in any of the lifecycle methods and even the render method.

    import React, { Component } from "react";
    import ThemeContext from "../Context/ThemeContext";
    import AppTheme from "../Colors";
    
    class Main extends Component{
        constructor(){
            super();
        }
        static contextType = ThemeContext;
        render(){
            const currentTheme = AppTheme[this.context];
            return(
                <main></main>
            );
        }
    
    }
    

    After assigning ThemeContext to the contextType property of our class, I saved the current theme object in the currentTheme variable.

    Now, we will grab the colors from the currentTheme variable and use them to style some markup.

    render() {
            const currentTheme = AppTheme[this.context];
            return (
                <main style={{
                    padding: "1rem",
                    backgroundColor: `${currentTheme.backgroundColor}`,
                    color: `${currentTheme.textColor}`,
    
                }}>
                    <h1>Heading 1</h1>
                    <p>This is a paragraph</p>
                    <button> This is a button</button>
                </main>
    

    That’s it! This method, however, limits you to consuming only one context.

  2. The second method is ThemeContext.Consumer that involves the use of a Consumer. Each context object also comes with a Consumer React component which can be used in a class-based component. The consumer component takes a child as a function and that function returns a React node. The current context value is passed to that function as an argument.

    Now, let us replace the code in our MainWithClass component with this:

    class Main extends Component {
        constructor() {
            super();
            this.state = {
            }
        }
        render(){
                   return(
                        <ThemeContext.Consumer>
                       {
                        (theme) => {
                            const currentTheme = AppTheme[theme];
                            return(
                                <main style = {{
                                    padding: "1rem",
                                    backgroundColor: `${currentTheme.backgroundColor}`,
                                    color: `${currentTheme.textColor}`,
                                
                                }}>
                                    <h1>Heading 1</h1>
                                    <p>This is a paragraph</p>
                                    <button> This is a button</button>
                                </main>
                            )
                           
                        }
                    }
                </ThemeContext.Consumer>
            );
        }
    
    }
    

    As you can see, we used the current value of our ThemeContext which we aliased as “theme” and we grabbed the color values for that theme mode and assigned it to the variable currentTheme. With this method, you can use multiple Consumers.

Those are the two methods of consuming context with class-based components.

Consuming Context With Functional Components

Consuming context with functional components is easier and less tedious than doing so with class-based components. To consume context in a functional component, we will use a hook called useContext.

Here is what consuming our ThemeContext with a functional component would look like:

const Main = () => {
    const theme = useContext(ThemeContext);
    const currentTheme = AppTheme[theme];
    return(
        <main style = {{
            padding: "1rem",
            backgroundColor: `${currentTheme.backgroundColor}`,
            color: `${currentTheme.textColor}`,
        
        }}>
            <h1>Heading 1</h1>
            <p>This is a paragraph</p>
            <button> This is a button</button>
        </main>
    );
}

export default Main;

As you can see, all we had to do was use our useContext hook with our ThemeContext passed in as an argument.

Note: You have to use these different components in the App.js file in order to see the results.

Updating Our Theme With The ThemeToggler Component

Now we are going to work on our ThemeToggler component. We need to be able to switch between the light and dark themes. To do this, we are going to need to edit our ThemeContext.js. Our React.createContext will now take an object resembling the result of a useState hook as an argument.

const ThemeContext = React.createContext(["light", () => {}]);

We passed an array to the React.createContext function. The first element in the array is the current theme mode and the second element is the function that would be used to update the theme. As I said, this just resembles the result of a useState hook but it is not exactly the result of a useState hook.

Now we will edit our App.js file. We need to change the value passed to the provider to a useState hook. Now the value of our Theme Context is a useState hook whose default value is “light”.

function App() {
  const themeHook = useState("light");
  return (
    <ThemeContext.Provider value = {themeHook}>
      <div>
        <Header />
        <Main />
        
      </div>
    </ThemeContext.Provider>
  );
}

Writing Our ThemeToggler Component

Let us now actually write our ThemeToggler component:

import React,{useContext} from "react";
import ThemeContext from "../Context/ThemeContext";

const themeTogglerStyle = {
    cursor: "pointer"
}
const ThemeToggler = () => {
    const[themeMode, setThemeMode] = useContext(ThemeContext);
    return(
        <div style = {themeTogglerStyle} onClick = {() => {setThemeMode(themeMode === "light"? "dark": "light")}}>
            <span title = "switch theme">
                {themeMode === "light" ? "🌙" : "☀️"}
            </span>
        </div>
    );
}

export default ThemeToggler;

Since the value of our theme context is now a hook whenever we call useContext on it, it will return an array. Using destructuring, we were able to grab the elements from the array. We then wrote an onClick event handler for our ThemeToggler. With that code, whenever the theme toggler is clicked, it will switch the theme of our application.

Now we will edit the different versions of our Main component.

Editing Our MainWithClass Component

  1. The version of the MainWithClass component that uses the Class.contextType method:
    import React, { Component } from "react";
    import ThemeContext from "../Context/ThemeContext";
    import AppTheme from "../Colors";
    
    class Main extends Component{
        constructor(){
            super();
        }
        static contextType = ThemeContext;
        render(){
            const currentTheme = AppTheme[this.context[0]];
            return(
                <main style={{
                    padding: "1rem",
                    backgroundColor: `${currentTheme.backgroundColor}`,
                    color: `${currentTheme.textColor}`,
    
                }}>
                    <h1>Heading 1</h1>
                    <p>This is a paragraph</p>
                    <button> This is a button</button>
                </main>
    
            );
        }
    
    }
    
  2. The version of the MainWithClass component that uses the ThemeContext.Consumer method:
    import React, { Component } from "react";
    import ThemeContext from "../Context/ThemeContext";
    import AppTheme from "../Colors";
    
    class Main extends Component {
        constructor() {
            super();
            this.state = {}
        }
        render() {
            return (
                <ThemeContext.Consumer>
                    {
                        ([theme]) => {
                            const currentTheme = AppTheme[theme];
                            return(
                                <main style = {{
                                    padding: "1rem",
                                    backgroundColor: `${currentTheme.backgroundColor}`,
                                    color: `${currentTheme.textColor}`,
                                
                                }}>
                                    <h1>Heading 1</h1>
                                    <p>This is a paragraph</p>
                                    <button> This is a button</button>
                                </main>
                            )
                           
                        }
                    }
                </ThemeContext.Consumer>
            );
        }
    
    }
    export default Main;
    

Editing Our MainWithFunction Component

The MainWithFunction Component should be edited as the following:

import React, { useContext } from "react";
import ThemeContext from "../Context/ThemeContext";
import AppTheme from "../Colors";


const Main = () => {
    const theme = useContext(ThemeContext)[0];
    const currentTheme = AppTheme[theme];
    return(
        <main style = {{
            padding: "1rem",
            backgroundColor: `${currentTheme.backgroundColor}`,
            color: `${currentTheme.textColor}`,        
        }}>
            <h1>Heading 1</h1>
            <p>This is a paragraph</p>
            <button> This is a button</button>
        </main>
    );
}

export default Main;

Conclusion

That’s it! We have succeeded in implementing two theme modes for our React app using the Context API.

In the process, we have learned:

  • What the Context API is and the problem it solves;
  • When to use the Context API;
  • Creating Context and consuming it in both functional and class-based components.

Further Reading on SmashingMag:

(dm, il)
Categories: Others Tags:
  1. No comments yet.
  1. No trackbacks yet.
You must be logged in to post a comment.