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.

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

To use context effectively you need 3 things:

A state object A way to change state from consumers 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 ; obj2 . pugson == obj . pugson ;

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.

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.

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.

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.

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.

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.

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

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.

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.

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.

"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:

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 > ) ; } ;

Did you enjoy this article? 👎 👍

Published on January 2nd, 2019 in Front End, Technical

Learned something new?

Want to become a high value JavaScript expert? Here's how it works 👇 Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths. Start with an interactive cheatsheet 📖 Then get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career. "Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌" ~ Ashish Kumar Your Name Your Email Your Address Subscribe & Become an expert 💌 Join over 10,000 engineers just like you already improving their JS careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

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

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

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 ❤️