[React]: Use global state with React Hook and Context
React
04/29/2020
TL;DR
Scroll down to 1. Set up Context for code snippets
Overview
In React, passing down state to child components can be tedious, especially when you have to share state with a "grand child" component i.e. performing a "prop drilling", or when you need to share a state with number of child components. This is when React Context
may be handy. Context
provides a way to pass data through component tree without having to pass them manually. This post will show how to pass it along with React hooks
.
Why you might need global state: to avoid prop drilling
In case where GrandChild
component needs a prop that needs to trickle all the way down from parent component, it'd be a bad practice to manually pass it down. It would be even worse if Child1
and Child2
don't need the "drilledProp"
import React from "react"import Parent from "../Parent/index"import GrandChild from "../GrandChild"
const Child2 = ({ drilledProp }) => { return ( <div> <GrandChild drilledProp={drilledProp} /> </div> )}
const Child1 = ({ drilledProp }) => { return ( <div> <Child2 drilledProp={drilledProp} /> </div> )}
const App = () => { const [someState] = React.useState("Hello") return ( <Parent> <Child1 drilledProp={someState} /> </Parent> )}
export default App
File structure for this post
.├── components| ├── App| | └── index.js| ├── Parent| | └── index.js| └── GrandChild| └── index.js├── contexts| └── user-context.js└── hooks └── use-user.js
Scenario
Consider a situation where you have a parent component i.e. App component or Layout component, and you would like to pass down its state and/or set state function to a "grand child" component that is located far down in a virtual DOM tree. Or you would like to pass down to many children components.
As an example, in a Parent
component, you want to pass user
state and setUser
function to a grand child component.
import React, { useState, useEffect } from "react"
const Parent = ({ children }) => { const [user, setUser] = useState(null)
useEffect(() => { setUser({ name: "Jon", age: 6 }) }, [])
return <div className="parent">{children}</div> }
export default Parent
And inside GrandChild
component, you want to use the users
property that was pass from far above in component tree.
Example:
import React from "react"
const GrandChild = () => { return ( <div> <h1>{user}</h1> </div> )}
export default GrandChild
1. Set up Context
Setting up context is simple. UserContext
will later contain user
state and setUser
function when it's passed down to a child component
import { createContext } from "react"export const UserContext = createContext({})
2. Set up hooks
This hook utilizes the context above and helps to retrieve the state in child components. The function will later be called with const { user, setUser } = useUser()
import { useContext } from "react"import { UserContext } from "../contexts/user-context"
export const useUser = () => { const [user, setUser] = useContext(UserContext)
return { user, setUser, }}
3. Context.Provider
Now, you'll need a way to pass the data from the parent component and determine which data to pass in. Use the <UserContext.Provider>
to wrap it around your child component. Provider
comes with Context
object, and it allows multiple components to "consume" to subscribe to context changes. It can trickle deep within the component tree.
import React, { useState, useEffect } from "react"import { UserContext } from "../../contexts/user-context"
const Parent = ({ children }) => { const [user, setUser] = useState(null)
useEffect(() => { setUser({ name: "Jon", age: 6 }) }, [])
return ( <div className="parent"> <UserContext.Provider value={[user, setUser]}> {children} </UserContext.Provider> </div> )}
export default Parent
4. Consume the passed state
This is where you use useUser
hook to "consume" the state stored in UserContext
which was passed from Parent
component.
import React from "react"import { useUser } from "../../hooks/use-user"
const GrandChild = () => { const { user } = useUser()
return ( <div> <h1>{`${user && user.name}`}</h1> </div> )}
export default GrandChild
5. App.js
Parent
component uses children
prop, so it assumes that Parent
and GrandChild
are rendered elsewhere. Here's an example.
import React from "react"import Parent from "../Parent/index"import GrandChild from "../GrandChild"
const App = () => { return ( <Parent> <GrandChild /> </Parent> )}
export default App
- [React + Node.js]: Create Messenger App with MERN Stack + Socket.io
- [Next.js]: Set up Sass/Scss environment with Next.js