Keeping track of React state with useContext()

matthewkohn

Matthew Kohn

Posted on October 6, 2022

Keeping track of React state with useContext()

In my time learning React at Flatiron School, I came to really like the useContext hook. I remember at first, though, the concept of context was a little confusing.

I'm excited to share what I've learned, and I hope it helps you.

Table of Contents

This article walks through how to set up and use a Context component in a React application, using functional component syntax. This article assumes you know the basics of React, and moves past some concepts quickly. Note the links abound for elaboration, if you'd like to dig deeper into React Context.

It's important to understand how hooks, props & state works with functional components before you dive into using context to control global state.

A couple brief definitions to warm us up:

  • Hooks: functions that let you “hook into” React state and lifecycle features from function components.
  • Props: arbitrary properties that are passed down from parent to child components.
  • State: the structure that keeps track of how data changes over time.

What is useContext()?

(back to top)

The useContext hook is a way to manage global state without needing to pass state as props. When your component tree starts to grow, you might notice certain state that you need among sibling components, like a student's id or items in a cart.

One solution might be to move that state to the nearest parent component, but that might be all the way up to the App component, and you might have to prop-drill to manage that state.

Basic Component Tree

A better solution might be to re-compose the structure of your component tree and pass state as props, but that might not be a reasonable solution depending on the complexity of your app.

This is where useContext() might come in handy. By keeping track of state in context & passing that context to the components can make your life a whole lot easier.

Component tree with context


Example for setting up useContext()

(back to top)
(skip to see result)

If you'd like to follow along, make sure you have a text editor and command line prompt handy.

Create a new React App called whatever you want, like "student-library".

npx create-react-app student-library
cd student-library
npm start
Enter fullscreen mode Exit fullscreen mode

1. Make a context file

(back to top)

Let's say we're trying to keep track of & update data about hypothetical students in our app.

Let's start writing code. Here's how I like to structure my files in my React App.

File Structure for React App


i. From your app's '/src', folder, create a folder called '/context', then from there make a file called "StudentContext.jsx".


ii. At the top of the file, import React, and start a function component called StudentProvider.

import React from "react";

function StudentProvider() {}
Enter fullscreen mode Exit fullscreen mode

iii. Now, use the createContext() function built into React to create a Context object.
Outside of/above the function declaration, make StudentContext:

const StudentContext = React.createContext();
Enter fullscreen mode Exit fullscreen mode

As React Docs say,

Every Context Object comes with a Provider React component that allows consuming components to subscribe to context changes.

We will use this Provider in the next steps to give other components access to our context's state.


iv. Return StudentContext.Provider inside the function component, wrapped around the special children props.

function StudentProvider({ children }) {
  return (
    <StudentContext.Provider>
      { children }
    </StudentContext.Provider>
  )
}
Enter fullscreen mode Exit fullscreen mode

v. At the end of the file, export StudentContext and StudentProvider.

export { StudentContext, StudentProvider }
Enter fullscreen mode Exit fullscreen mode

vi. Now, set up the student's state. Import the useState hook:

import React, { useState } from "react";
Enter fullscreen mode Exit fullscreen mode

vii. Make student state inside the function before the return statement, with an initial value of null.

const [student, setStudent] = useState(null);
Enter fullscreen mode Exit fullscreen mode

viii. Save the state & its setter in a variable, we'll call value.

const value = { student, setStudent };
Enter fullscreen mode Exit fullscreen mode

ix. Finally, pass this value as props in the StudentContext.Provider.

<StudentContext.Provider value={ value }>
Enter fullscreen mode Exit fullscreen mode

Here's the complete file for StudentContext.jsx:

(back to beginning of example)

// 'StudentContext.jsx'
-----
import React, { useState } from 'react';
const StudentContext = React.createContext();

function StudentProvider({ children }) {
  const [student, setStudent] = useState(null);
  const value = { student, setStudent };
  // console.log('student from StudentContext: ', student)
  return (
    <StudentContext.Provider value={ value }>
      { children }
    </StudentContext.Provider>
  )
}

export { StudentContext, StudentProvider }
Enter fullscreen mode Exit fullscreen mode

2. Wrap Provider around highest level component

(back to top)

According to React Docs,

When React renders a component that subscribes to this Context object it will read the current context value from the closest matching Provider above it in the tree.

Remember, the Provider is “providing” access to the Context component (and the state or other variables it may hold) anywhere you need to invoke it.

Question: Which components will you need to set or access student and setStudent in your app?

Find the top-most parent component they all have in common. This is where we will use our StudentProvider.

Since our context is helping us with global state, and for the sake of this example, we're moving all the way up to index.js in the '/src' folder. You can place this anywhere that works for your app, just make sure it's "above" anywhere you'll need it in the component hierarchy.

Import StudentProvider and wrap it around the root component, which in this case is <App/>.

// 'index.js'
-----
import React from 'react';
import ReactDOM from 'react-dom/client';
import { StudentProvider } from './context/StudentContext';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <StudentProvider>
    <App />
  </StudentProvider>
);
Enter fullscreen mode Exit fullscreen mode

Now, any child of the <App /> component is able to import StudentContext if they need to.


3. Using useContext()

(back to top)

Now you are able to access and set student state anywhere in your app.

Let's say you are using student data stored on an API. Make a new file under '/src/components/' called Student.jsx.

i. Start by importing React, useContext and StudentContext.

import React, { useContext } from 'react';
import { StudentContext } from '../context/StudentContext';
Enter fullscreen mode Exit fullscreen mode

ii. Destructure values inside the component function by passing StudentContext to the useContext() hook.

function Student () => {
  const { student, setStudent } = useContext(StudentContext);
  return (<></>)
}
Enter fullscreen mode Exit fullscreen mode

From here, you can fetch data from an API and store it with setStudent inside a useEffect hook, or create a form a user can fill out and save a form data object with setStudent.

Once you set your state with the Context Component you made, you can use that data however you want, passing the student variable to this or any other component in your app with the useContext hook.

You could use it to display a name, provide authorization to view hidden components, or store a schedule, shopping cart, or profile page, to name a few examples. It's really up to you from here what you want to do.


Conclusion

(back to top)

Using Context is easy & convenient once you know how it's set up and how it's used. You can keep track of errors, themes, authorization codes, or really anything you might need to keep track of global state.

Keep in mind, anytime you update Context State, the component using that state will re-render. If this isn’t the behavior you want, maybe a useMemo or useRef hook might be a better choice.

Please note: It is advised to use useContext sparingly because of performance issues when your app scales. Only use it if you need it.

Some React devs think that useContext is bad, for reasons worth considering. I like using it for themes & authentication in smaller dynamic apps, like my personal projects.

In the end, it's up to you to decide how to structure your app and make it respond to student interactions how you see fit. This is just one of many ways to keep track of state in a dynamic React application.

I hope this helps, and I wish you well. Happy coding!

💖 💪 🙅 🚩
matthewkohn
Matthew Kohn

Posted on October 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related