Sometimes when you're building an app in React, it can be difficult to pass props from one component to another without prop drilling. One of the in-built tools that React has to tackle this issue is the useContext hook.
The React's Context allows you to wrap your components in a context provider. The values which you need to share throughout your components can then be initialised at the top level and then accessed in any component using the useContext hook.
Let's take a look at a simple example.
Let's say we have an App which contains two components - one is a text input and the other is a component which will display the value that the user enters.
Here's the file structure.
Unfortunately, we can't simply pass the input value between the Input and Result siblings. In reality, in this very simple example, the best way of dealing with the issue would be to lift the state to the App component and then pass it down to each of the child components. But let's say you were building a more complex app and you needed to pass the state down several levels of the component tree while avoiding prop drilling - that's where Context comes in.
The starter files for this example can be found here.
First, we want to create a new file and create our context using React.createContext
.
import * as React from "react"
export type InputValues = {
nameValue: string
setNameValue: React.Dispatch<React.SetStateAction<string>>
}
export const InputContext = React.createContext<InputValues>({
nameValue: "",
setNameValue: () => console.info("Name not yet initialised"),
})
In the createContext object, you will need to add and initialise any values that you need to. Here, we've set up nameValue
which will be used in the Result component to display the name and the setNameValue
which will be used to set the value in the Input component.
Next, we'll create our own hook which we can use later in the provider.
import * as React from "react"
import { InputValues } from "./input-context"
export function useInputProvider(): InputValues {
const [nameValue, setNameValue] = React.useState("")
return {
nameValue,
setNameValue,
}
}
Here, we simply set up nameValue
and setNameValue
with the React useState hook and return them to use in our next step.
Now we need to go to our App file and wrap our Input and Result components in a context provider.
import { Input, Result } from "./components"
import { InputContext } from "./context"
import { useInputProvider } from "./context/use-input-provider"
function App() {
const nameValue = useInputProvider()
return (
<div className="inputForm">
<InputContext.Provider value={nameValue}>
<Input />
<Result />
</InputContext.Provider>
</div>
)
}
export default App
So, we use the InputContext
that we set up in the first step and wrap the child components in the provider. We can then use the useInputProvider
hook that we set up in the second step to pass nameValue
and setNameValue
as the Provider value.
Now that we've set up the provider, how do we access the values in our child components?
First, let's go to our Input component.
import * as React from "react"
import { InputContext } from "../context"
export function Input(): JSX.Element {
const { setNameValue } = React.useContext(InputContext)
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setNameValue(e.target.value)
}
return (
<form>
<label htmlFor="name">Name: </label>
<input type="text" id="name" name="name" onChange={handleChange} />
</form>
)
}
Here, we need to access setNameValue
in order to set the name value to whatever the user types in the input field. To do this, we can use the useContext
hook and pass in our InputContext
. You can then extract setNameValue
like this -
const { setNameValue } = React.useContext(InputContext)
You can then go ahead and use setNameValue
to take in the input value.
Finally, let's go over to our Result component and access nameValue
in the same way using useContext
.
import * as React from "react"
import { InputContext } from "../context"
export function Result() {
const { nameValue } = React.useContext(InputContext)
return <div>{nameValue}</div>
}
We can then pass the nameValue
into the <div>
to display the result.
And that's it! You can find the completed code here.
Top comments (0)