DEV Community

Cover image for How to Use Context with React Hooks
Mike Cronin
Mike Cronin

Posted on • Originally published at Medium

How to Use Context with React Hooks

Context is probably my favorite React feature, especially when using hooks. It’s not bleeding edge tech anymore, so you should take a second to learn how it works. We’re just going to create a Context component, and then read/set values from it in our main app. It’s going to be a very simple project, but one that shows the basics and how you can build on it in the future. Here is the code on GitHub.

What is Context?

Context lets you have global properties and functions that can be accessed from anywhere in your project. This is pretty much what Redux does, and the best way to distinguish Redux from Context is size: Context is smaller and simpler. The model for a Redux Store is usually a intricate, immutable object, whereas with Context it would be more helpful if you think about it like a floating component that can talk to any other component. You also don't have to use reducers, which can also drastically simplify things.

Setup

Use create-react-app and that's it. We aren't going to have any external dependencies. We're going to create a Context component, give it an internal state, and then share that state with the rest of our app. All our app is actually going to do is save an input string to Context. I encourage you to read them all the same. It's good to know both the hooks and state version, since your company may not be using the latest React.

Step 1: Create a Context component

In src/ create a context/ directory and inside it put index.js and ContextProvider.js. Let's fill out index.js first:

import React from 'react';
const AppContext = React.createContext({});
export default AppContext;
Enter fullscreen mode Exit fullscreen mode

I'm going to explain that second line, but first let's also create ContextProvider.js:

import React, { useState } from 'react';
import AppContext from '.';

const ContextProvider = ({ children }) => {
  const [example, setExample] = useState('Hello there')
  const context = {
    setExample,
    example,
  };
  return (
    <AppContext.Provider value={ context }> 
      {children}
    </AppContext.Provider>
  );
}

export default ContextProvider;
Enter fullscreen mode Exit fullscreen mode

Step 1a: What did we do

Alright, let's talk about createContext and AppContext.Provider. We actually create our Context in index.js, this is the "raw" Context if you will. See, context itself is really just a value, but React incorporates it into its system and gives it Consumer and Provider components. Now, hooks let us bypass the need for a Consumer component, but we still need to have a parent Provider component.

What our Provider component does is take a value (we're calling it context, but it can be named anything) and then make it accessible to any of the children components. This value is our global store. Also, if you aren't familiar with children props, we'll talk about it in the next step.

Internal state

Notice what we pass into our context value: it's a useState hook and its accompanying value. That's the best part about this setup, we're simply tracking a regular component's state. When an external component needs to update the store, there's no magic, it's merely updating the internal state of the Context component. That change is then updated wherever it gets read, like a different version of props. There's nothing new here except where the data is being stored. You can of course add as much as you like to this object, but for now we're keeping it pretty bare bones.

Step 2: Plug your Context into your app

In order for Context to do anything, we need to make it available. Any child component of our ContextProvider component will have access to the store. That means, we need to put it somewhere very high up in the component chain, so I usually put it at the top in the src/index.js file:

import React from 'react';
import ReactDOM from 'react-dom';
import ContextProvider from './context/ContextProvider';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <ContextProvider>
      <App />
    </ContextProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

This is also where the children prop in our ContextProvider comes into play. Recall our return statement in our provider component:

return (
  <AppContext.Provider value={ context }>
    {children}
  </AppContext.Provider>
);
Enter fullscreen mode Exit fullscreen mode

By nesting out <App> inside <ContextPrivider>, our main app and all its child components are now the children of the <AppContext.Provider> component. That's what actually lets our app get access to our Context and prevents unnecessary renders. Here's a quick article on props.children if you aren't familiar with it.

Step 3: Use your Context in a component

Alright, here we go! All we're going to make is a little form that lets us set the string value of example in our Context. And we'll display it with the useEffect hook and a console log. We're going to keep things simple and do it all in our main src/app.js file:

import React, { useContext, useState, useEffect } from 'react';
import './App.css';
import AppContext from './context';
const App = () => {
  const { example, setExample } = useContext(AppContext);
  const [formText, setFormText] = useState('');
  useEffect(() => {
    console.log('context here: ', example);
  }, [example]);

  const handleChange = (e) => {
    setFormText(e.target.value);
  };
  const handleSubmit = (e) => {
    e.preventDefault();
    setExample(formText);
  };

  return (
    <div className="App">
    <form onSubmit={handleSubmit}>
      <label htmlFor="example">Example: </label>
      <input
        type='text'
        value={formText}
        onChange={handleChange}
      />
      <button>DO IT</button>
    </form>
    </div>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

There's the whole thing, and here are the parts that use Context:

import AppContext from './context'; 
// ...
const App = () => {
  const { example, setExample } = useContext(AppContext);
  useEffect(() => {
    console.log('context here: ', example);
  }, [example]);
// ...
  const handleSubmit = (e) => {
    e.preventDefault();
    setExample(formText);
  };

  return (
// ...
      <input
        type='text'
        value={formText}
        onChange={handleChange}
      />
Enter fullscreen mode Exit fullscreen mode

We just feed our Context into the useContext hook and then pull out the properties we want to use. The way you use those properties is pretty much the same as you would a useState function or value. Remember the Context object is the one defined in index not the ContextProvider component, that's only ever used in a single place. This is surprisingly simple, but that's all thanks to hooks. They all work together seamlessly that Context really fits right in.

That's pretty much it

There used to be a bit more pomp and circumstance when using Context with class based components, but hooks make it like another useState (if you need class based components, check out Wes Bos's tutorial, I just adapted the pattern for hooks). And of course things can get more complex, like multiple Contexts or a useReducer instead of useState, but at it's core, it's a simple concept.

happy coding everyone,
mike

Top comments (0)