Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    How to use React Context effectively

    Modern React Context has been with us for a while but I still see a lot of confusion about how to use it effectively.

    A lot of people feel that no matter what, you still need some form of state management. Either an existing library or you end up building your own anyway.

    Yes, sort of. But not in the way you think.

    Redux and MobX have always used context behind the scenes. They're familiar, you can use them right away, and your workflow doesn't have to change. Congratz, you're using React Context effectively.

    Maybe you don't like or need the complexity of Redux and MobX. Overhead, bundle sizes, indirection and separation of concerns way beyond what your tiny app needs. Lots of reasons.

    That's where modern React Context comes in.

    Work with context directly

    Here's a video of mine explaining React Context in 2 minutes 👇

    To use context effectively you need 3 things:

    1. A state object
    2. A way to change state from consumers
    3. A way to communicate state changes to your provider

    That is all.

    A state object is where you hold your state tree. Many ways to build one, I prefer plain JavaScript objects. A reducer-like approach is also popular.

    Then you need some way to change said state. I like to use functions attached to the state object itself. That way anyone who has access to state also has the ability to change values. Functions are best. Think of them as actions.

    Finally, you need a way for your Provider to know that state changed. This is important because React uses the same props-changed tree diffing algorithm to update context as it does for reconciliation and re-rendering.

    JavaScript uses shallow object comparisons, so relying on component state is best. React comes with all necessary machinery built-in.

    Here's a bug you can run into if you're not careful:

    let obj = {
      pugson: {
        greet: "Hello",
      },
    }
    
    let obj2 = { ...obj }
    
    obj2 == obj // false
    obj2.pugson == obj.pugson // true
    

    Even though we made a copy of obj, their internal properties remained equal. The pugson object is shared between both.

    So if you're passing deep properties into your context or your props, your app is going to break.

    An example CodeSandbox

    Okay, so you're gonna need some state, a way to change it, and a way to communicate changes. Here's what that would look like in code 👇

    You can say hi to Pugson. Type into the input box and see what you're typing right away.

    It's a little contrived, but that makes it easier to explain. There are a few moving pieces.

    1. The Context itself

    I like to define the context itself in its own file. You could have a file with multiple context definitions for different parts of your app.

    This creates a convenient way for different components to share the same context via import statements.

    // GreetContext.js
    import React from "react"
    
    const { Provider, Consumer } = React.createContext()
    
    export { Provider, Consumer }
    

    Create context and export its Provider and Consumer. Providers pass values down the tree of components, and consumers use them to do stuff.

    2. The App component holds and provides state

    Like I said, the simplest way to keep state that communicates changes is with component state. You should have a single source of truth for your entire app like always.

    // App.js
    class App extends React.Component {
      state = {
        greeting: "",
        setGreeting: ({ value }) => this.setState({ greeting: value }),
      }
      render() {
        return (
          <div class="App">
            <provider value={this.state}>
              <form>
                <greeting></greeting>
              </form>
            </provider>
          </div>
        )
      }
    }
    

    The state object has both values and setters. greeting is the string you're typing with a default value of "", and setGreeting is the setter any component can use to change the greeting.

    Since we're using component state, the setter can call setState and let React figure out the rest.

    My favorite side-effect of this approach is that important parts of your code are close together. You can start thinking of your state as a state machine because states and their transitions are next to each other.

    As your state grows in complexity, you might want to move this machinery out of App.js into its own file. Just make sure your App component knows when something changes.

    When rendering we use Provide to pass this component state as a context value down to our entire component tree.

    3. Consuming context state

    Consuming your state is a matter of rendering a ``and using the values it provides.

    // Greeting.js
    import React from "react"
    
    import { Consumer } from "./GreetContext"
    
    export default () => (
      <consumer>
        {({ greeting }) => (
          <div>
            <h3>Your greeting 👋</h3>
            {greeting}
          </div>
        )}
      </consumer>
    )
    

    See how keeping the context definition in a separate file makes it more convenient to use?

    We import the Consumer, render it as the root of our Greeting component, and pass-in a function as children. The good old render props approach but with children.

    Since our context value is an object, we can destructure it right away and take out just what we need: greeting. Then render as usual.

    Whenever the greeting value changes, our component will automatically re-render and show the new value.

    4. Changing context state

    Changing our greeting value works in a similar way. We render a consumer, take out the setter, and pass it as a prop into a component that does the changes.

    But there’s no way to access it outside of render, is there?

    @swizec waiting for your expert answer. Maybe I’ve been doing unstated wrong the whole time

    This also lets you solve the problem that you can't access context value outside of the render method. It's true, you cannot, and that's why you pass them into child components and use it there.

    // Form.js
    import React from "react";
    
    import { Consumer } from "./GreetContext";
    
    const Input = ({ value, onChange }) => (
      <input value={value} onchange="{event" ==""> onChange({ value: event.target.value })}
        style={{ width: "100%", fontSize: "1.5em" }}
      />
    );
    
    export default () => (
      <consumer>
        {({ greeting, setGreeting }) => (
          <div>
            <h1>Say hi to Pugson</h1>
            <img src="./img/profile_images-834049142515187713-cOtVTgLm_400x400.jpg" style={{ height: "60px" }}>
            <input value={greeting} onchange={setGreeting}>
          </div>
        )}
      </consumer>
    );
    

    Once more, we import that same shared context Consumer, render, and use a function as children approach. This time, we take both the greeting and setGreeting out of our context value.

    We pass those into the Input component as value and the onChange callback. This allows Input to be as complex or as simple as it wants, makes it a fully controlled component, and most importantly, it doesn't rely on context.

    A common foot-gun is to make your components so tied to context or state management that you can't reuse them. Always a good idea to make your basic components rely on props alone.

    Some modern context-based libraries

    "But that's an awful lot like re-inventing your own state management", I hear you say.

    Kind of? You're using out-of-the-box React tools. No wheel reinventing required.

    But yes, as complexity grows, you start moving this machinery out of your main component into its own files, and start running into a lot of similar problems as you would with building your own state management library.

    When that happens, I recommend splitting your context into subcontexts. Have a new context with a new state object for every section of your app.

    A form could have its own state and context, for example, use it to communicate between all the fields, then communicate its end result to the parent context. Much like nested redux reducers or something.

    You can also make some of this stuff easier with modern state management libraries like Constate or Unstated.

    I won't go into detail on those, so here's two videos:

    What about hooks?

    Yes, this approach works with hooks. Replace ``with useContext. Everything else stays the same.

    import GreetingContext from "./GreetingContext"
    export default () => {
      const { greeting } = useContext(GreetingContext)
    
      return (
        <div>
          <h3>Your greeting 👋</h3>
          {greeting}
        </div>
      )
    }
    
    Published on January 2nd, 2019 in Front End, Technical

    Did you enjoy this article?

    Continue reading about How to use React Context effectively

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️