DEV Community

Cover image for Using React Context
Akhila Ariyachandra
Akhila Ariyachandra

Posted on • Updated on • Originally published at akhilaariyachandra.com

Using React Context

This was originally posted on my blog on May 7th, 2020. Check it out for my posts on React and JavaScript.

While developing a React app you might come across a situation where you need to share a value or state between components. If the state needs to be shared between two components and they have a direct parent-child relationship, we can pass the state from the parent to the child through props. Or if we want to share the state within multiple components we might look into something like Redux.

If you want to learn how to setup Redux in a React App, check out my post here. If you want to use Redux with hooks check out my post here.

If the state to be shared isn't complex, Redux might be overkill as it takes some effort to setup and use. For those cases we can use React Context.

What we will be building

Application Preview

We will build an app that fetches and displays users from JSONPlaceholder.

The app will be divided into three parts.

  • Context - Used to share the state within the app.
  • Controls - The component used to change the user.
  • Display - The component used to display the User data.

The Context

The Context will share any value given to it to its direct descendants.

In our case, we will need to share four sets of data.

  • userId - The state that holds the current User ID.
  • setUserId - The function that updates the userId state.
  • user - The state the holds the User data.
  • isFetching - The state that will be used to indicate if the app is currently in the middle of fetching a user so that the controls can be disabled.

To start, create the context folder and in it create the UserContext.js file.

import React from "react";
Enter fullscreen mode Exit fullscreen mode

Next let's create and export the context with some default values.

export const UserContext = React.createContext({
  userId: 1,
  setUserId: null,
  user: null,
  isFetching: false,
});
Enter fullscreen mode Exit fullscreen mode

After that we'll declare a Context Provider which will expose the context to its child components.

export const UserProvider = ({ children }) => {
  return <UserContext.Provider>{children}</UserContext.Provider>;
};
Enter fullscreen mode Exit fullscreen mode

Then let's declare the userId, user and isFetching states and pass them to the provider.

export const UserProvider = ({ children }) => {
  const [userId, setUserId] = React.useState(1);
  const [user, setUser] = React.useState(null);
  const [isFetching, setIsFetching] = React.useState(false);

  return (
    <UserContext.Provider value={{ userId, setUserId, user, isFetching }}>
      {children}
    </UserContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now we'll setup an effect to automatically update the user state whenever the userId state is changed.

const fetchUser = async () => {
  try {
    setIsFetching(true);

    const response = await fetch(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    const responseJson = await response.json();

    setUser(responseJson);
  } catch (error) {
    console.error("> Error fetching user: ", error);
  } finally {
    setIsFetching(false);
  }
};

React.useEffect(() => {
  fetchUser();
}, [userId]);
Enter fullscreen mode Exit fullscreen mode

Finally the UserContext.js file should look like this.

import React from "react";

export const UserContext = React.createContext({
  userId: 1,
  setUserId: null,
  user: null,
  isFetching: false,
});

export const UserProvider = ({ children }) => {
  const [userId, setUserId] = React.useState(1);
  const [user, setUser] = React.useState(null);
  const [isFetching, setIsFetching] = React.useState(false);

  const fetchUser = async () => {
    try {
      setIsFetching(true);

      const response = await fetch(
        `https://jsonplaceholder.typicode.com/users/${userId}`
      );
      const responseJson = await response.json();

      setUser(responseJson);
    } catch (error) {
      console.error("> Error fetching user: ", error);
    } finally {
      setIsFetching(false);
    }
  };

  React.useEffect(() => {
    fetchUser();
  }, [userId]);

  return (
    <UserContext.Provider value={{ userId, setUserId, user, isFetching }}>
      {children}
    </UserContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

The Display component

Next let's create the Display component. Create the components folder and in it add the file Display.js.

import React from "react";

const Display = () => {
  return <div></div>;
};

export default Display;
Enter fullscreen mode Exit fullscreen mode

Now we can get the user state by from the UserContext with the useContext hook.

import { UserContext } from "../context/UserContext";

const Display = () => {
  const { user } = React.useContext(UserContext);

  return <div></div>;
};
Enter fullscreen mode Exit fullscreen mode

To finish off the Display component, let's display the User data in a table.

return (
  <div>
    <table>
      <tbody>
        <tr>
          <td>ID: </td>
          <td>{user?.id}</td>
        </tr>

        <tr>
          <td>Name: </td>
          <td>{user?.name}</td>
        </tr>

        <tr>
          <td>Username: </td>
          <td>{user?.username}</td>
        </tr>

        <tr>
          <td>Email: </td>
          <td>{user?.email}</td>
        </tr>
      </tbody>
    </table>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

In the end the Display component should look like this.

import React from "react";
import { UserContext } from "../context/UserContext";

const Display = () => {
  const { user } = React.useContext(UserContext);

  return (
    <div>
      <table>
        <tbody>
          <tr>
            <td>ID: </td>
            <td>{user?.id}</td>
          </tr>

          <tr>
            <td>Name: </td>
            <td>{user?.name}</td>
          </tr>

          <tr>
            <td>Username: </td>
            <td>{user?.username}</td>
          </tr>

          <tr>
            <td>Email: </td>
            <td>{user?.email}</td>
          </tr>
        </tbody>
      </table>
    </div>
  );
};

export default Display;
Enter fullscreen mode Exit fullscreen mode

The Controls component

The Controls component is used to change the current userId.

To start create the Controls.js file in the components folder.

import React from "react";

const Controls = () => {
  return <div></div>;
};

export default Controls;
Enter fullscreen mode Exit fullscreen mode

After that, we can get userId, setUserId and isFetching from UserContext.

import { UserContext } from "../context/UserContext";

const Controls = () => {
  const { userId, setUserId, isFetching } = React.useContext(UserContext);

  return <div></div>;
};
Enter fullscreen mode Exit fullscreen mode

Next we can add two buttons to change the userId.

return (
  <div>
    <button onClick={() => setUserId(userId - 1)}>previous</button>

    <button onClick={() => setUserId(userId + 1)}>next</button>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

Finally we will add a check to the buttons to disable them if the app is already fetching a user or to stop userId from being set to value less than 1 or more than 10.

return (
  <div>
    <button
      onClick={() => setUserId(userId - 1)}
      disabled={userId <= 1 || isFetching}
    >
      previous
    </button>

    <button
      onClick={() => setUserId(userId + 1)}
      disabled={userId >= 10 || isFetching}
    >
      next
    </button>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

Finally Controls.js file should look like this.

import React from "react";
import { UserContext } from "../context/UserContext";

const Controls = () => {
  const { userId, setUserId, isFetching } = React.useContext(UserContext);

  return (
    <div>
      <button
        onClick={() => setUserId(userId - 1)}
        disabled={userId <= 1 || isFetching}
      >
        previous
      </button>

      <button
        onClick={() => setUserId(userId + 1)}
        disabled={userId >= 10 || isFetching}
      >
        next
      </button>
    </div>
  );
};

export default Controls;
Enter fullscreen mode Exit fullscreen mode

Bringing it all together

Now all that's left to is to bring everything together in the root component.

import React from "react";

const App = () => {
  return <div className="App"></div>;
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Then we should wrap the root div in the UserProvider to make the context available to all components.

return (
  <UserProvider>
    <div className="App"></div>
  </UserProvider>
);
Enter fullscreen mode Exit fullscreen mode

Finally add the Display and Controls components.

return (
  <UserProvider>
    <div className="App">
      <Display />

      <Controls />
    </div>
  </UserProvider>
);
Enter fullscreen mode Exit fullscreen mode

In the end the root component should look like this.

import React from "react";
import Display from "./components/Display";
import Controls from "./components/Controls";
import { UserProvider } from "./context/UserContext";

const App = () => {
  return (
    <UserProvider>
      <div className="App">
        <Display />

        <Controls />
      </div>
    </UserProvider>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Wrapping up

Below is a sample of the app we just built. If you think you missed something, feel free to check out the code.

If you found this post helpful please be sure to share it! 😊

Top comments (0)