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

JSX
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

TEXT
.
├── 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.

/components/Parent/index.js
JS
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:

/component/GrandChild/index.js
JS
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

/contexts/user-context.js
JS
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()

/hooks/use-user.js
JS
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.

/components/Parent/index.js
JSX
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.

/components/GrandChild/index.js
JSX
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.

/component/App/index.js
JSX
import React from "react"
import Parent from "../Parent/index"
import GrandChild from "../GrandChild"
const App = () => {
return (
<Parent>
<GrandChild />
</Parent>
)
}
export default App

WRITTEN BY

Keeping a record