Mark Thomas Miller's logo

Updating React Context inside child components

October 28, 2019

If you want to update context from inside a child component, you can use one of the following methods. I've split this guide into two parts: one for React hooks, and one for classes.

If you're using context via React hooks

React Hooks provide a clean and simple approach to context. The end result will allow you to use an API like:

function YourComponent() {
	const { state, update } = useContext(ExampleContext)

	// `state` is an object containing all your context variables.
	console.log(state) // { foo: "bar", baz: "qux" }

	// `update` is a function that lets you update the variables; for instance:
	update({ foo: "garply" })
}

In order to do this, you'll want to create a new file to hold your context. I like to put this in a high-level folder (usually directly under src) so it's easy to import.

Create a new file for your context; for instance, ExampleContext.js. Set it up like this:

import React, { createContext, useReducer } from 'react'

export const ExampleContext = createContext()

const reducer = (state, pair) => ({ ...state, ...pair })

const initialState = {
	sound: "bark"
}

export function ExampleProvider(props) {
	const [state, update] = useReducer(reducer, initialState)

	return (
		<ExampleContext.Provider value={{ state, update }}>
			{props.children}
		</ExampleContext.Provider>
	)
}

Now, you'll want to wrap one of your higher-level components (such as App.js) with your provider component. (I'll give an example of this in a moment.) The provider's job is to quite literally provide context to any child component. This means that only its child elements will have access to this context:

<App>                             # ❌ can't access context
	<SomeParent>                    # ❌ can't access context
		<ExampleProvider>
			<SomeChild>                 # ✅ can access context
				<SomeGrandchild>          # ✅ can access context
					<div>                   # ✅ can access context
						<SomeGreatGrandchild> # ✅ can access context

When one of your variables in context changes, every child component will re-render with the new data, so you'll want to put this at an appropriate level. (In the example above, if only SomeGreatGrandchild needed to have access to context, you would want to move ExampleProvider closer to it to prevent unnecessary re-renders.)

Anyway, I promised you an example, so here it is. Let's say that you wanted your entire app to have access to the context (which is pretty common in smaller apps). You could wrap your App component in the provider:

import { ExampleProvider } from './context/ExampleContext'

function App() {
	return (
		<ExampleProvider>
			...the rest of your app...
		</ExampleProvider>
	)
}

Now, you can import ExampleContext and destructure the state and update variables from it. They can be used like this:

import { ExampleContext } from './context/ExampleContext'

function YourComponent() {
	const { state, update } = useContext(ExampleContext)

	console.log(state.sound) // "bark"

	const handleClick = () => update({ sound: "meow" })

	return (
		<button onClick={handleClick}>
			Click to change sound to meow
		</button>
	)
}

And there you have it! Hopefully this was able to help you a bit.

If you're using context via React classes

If you're using context via classes, the process is somewhat similar.

  1. Add a method called updateState to your provider, which calls this.setState. Then, attach it to your state:

    // DataProvider.jsx
    
    import React, { Component } from 'react'
    import DataContext from 'path/to/your/DataContext'
    
    class DataProvider extends Component {
    	constructor(props) {
    		super(props)
    		this.updateState = this.updateState.bind(this) // ← Here
    		this.state = {
    			foobar: '...'
    			update: this.updateState // ← Here
    		}
    	}
    
    	updateState(values) { // ← And here
    		this.setState(values)
    	}
    
    	render() {
    		return (
    			<DataContext.Provider value={this.state}>
    				{this.props.children}
    			</DataContext.Provider>
    		)
    	}
    }
    
    export default DataProvider
    
  2. Now, inside the child component, you can use this.context.update to update the state. It works exactly like this.setState, so you can pass it a key: value pair.

    // Grandchild.jsx
    
    import React, { Component } from 'react'
    import DataContext from 'path/to/your/DataContext'
    
    class Grandchild extends Component {
    	componentDidMount() {
    		this.context.update({ foobar: 'It worked!' }) // ← Here
    	}
    
    	render() {
    		return <div>{this.context.foobar}</div>
    	}
    }
    
    Grandchild.contextType = DataContext
    
    export default Grandchild
    

That's all there is to it: you've updated a React component's state from inside a child component via Context. If you need more guidance, I recommend reading my guide on setting up React Context.