Props help you pass down data from parent to child components. By so doing, it's using the flow system of data passage (top to bottom) to get that done. However, when the project grows, it becomes more complex using props to pass down data from one component to another that is not directly a child of it, or where the said component is deeply nested in the application.
Sometimes, the components in-between the parent and the child component may not need this information but they are obliged to pass it down until it gets to the component that needs it. This can be a heck of a problem and this is where you need the ContextAPI().
What is ContextAPI
The ContextAPI helps you pass props or state through the component tree without having to do it manually. It helps you pass information from the parent component to several other components in the application no matter how deeply nested they are. With useContext, the information tunnels through from the parent component to the component that needs it through the use of context. It helps solve the prop drilling problem encountered in React state management.
Some types of data are too complex to be shared through props. Data such as Auth and theme can be passed through context without compromising your application.
ContextAPI is an in-built React library so you don't need to bother about increasing your build size or having to install a third-party library.
With useContext, any component that needs information or prop from the parent component can easily ask for it and have access to it right away with the use of context.
Prop-Drilling
Prop-drilling is the process of passing props from a parent component down to child components no matter how deeply nested they are. The passing of data is done manually from component to component until the desired component is reached.
Below is a diagram and an example of prop-drilling.
In this diagram, the PARENT component (APP) is to pass state around down to as far as PLAYERIDENTITY component. Even though the DASHBOARD component doesn't have a need for this state, the data had to pass through it to get to the other children components: PLAYERSTATE and PLAYER. PLAYERSTATE consumed its share of state; activeplayer and PLAYER component passed on the state to PLAYERSEX and PLAYERNAME even though it didn't have any use for them. PLAYERSEX received the sex of the player and outputted it, and PLAYERNAME sent the last prop down to PLAYERIDENTITY.
Let's see this diagram in code, so you get to understand prop-drilling better.
import { useState } from 'react'
import './App.css'
import Dashboard from './Dashboard'
function App() {
const [sex, setSex] = useState('male')
const [name, setName] = useState('Louisa')
const [activePlayer, setActivePlayer] = useState('Yes')
return (
<div className="App">
<h3> Welcome {name}</h3>
<Dashboard {...{sex, name, activePlayer}} />
</div>
)
}
export default App
import React from 'react'
import PlayerState from './PlayerState'
import Player from './Player'
const Dashboard = ({sex, name, activePlayer}) => {
return (
<div>
<PlayerState {...{ activePlayer }} />
<Player {...{ sex, name }} />
</div>
)
}
export default Dashboard
import React from 'react'
const PlayerState = ({activePlayer}) => {
return (
<div className='player-state'>
<h4>Is the player active? { activePlayer}</h4>
</div>
)
}
export default PlayerState
import React from 'react'
import PlayerSex from './PlayerSex'
import Playername from './Playername'
const Player = ({sex, name}) => {
return (
<div className='player'>
<PlayerSex {...{ sex }} />
<Playername {...{ name }} />
</div>
)
}
export default Player
import React from 'react'
const PlayerSex = ({sex}) => {
return (
<div>Can you guess player sex? { sex}</div>
)
}
export default PlayerSex
import React from 'react'
import PlayerIdentity from './PlayerIdentity'
const Playername = ({name}) => {
return (
<div>
<h3>Just one more chance to guess player name right</h3>
<PlayerIdentity {...{ name }} />
</div>
)
}
export default Playername
import React from 'react'
const PlayerIdentity = ({name}) => {
return (
<div>{ name}</div>
)
}
export default PlayerIdentity
This process can be tedious and any slight change of code or correction can break the application. Also, it is prone to errors such as a typo which can ruin the whole process.
With context, a lot of this stress can be done away with. Let's see how useContext works.
The 3 Stages of useContext
There are three (3) stages of useContext.
createContext: This is the stage where the context is created using the createContext() function. It receives only one argument called defaultValue.
Provider: This is a component of the context. It is used to wrap all the components that need access to the information in the context. It receives a single prop called value. The value prop contains the value of your context (sounds like a tautology, but you will see what I mean later). All the components wrapped with the provider, and their children all have access to the value prop of the context.
Consumer: This is another component of the context. It is used to wrap the components that will have access to the components. When the Consumer is wrapped around a component, the value of the state can change, but when it's not, the value of the state remains the defaultValue.
How to use Context
To use context, you have to create it with an in-built function in the useContext hook called createContext. Now, let's create our context, and we will be using the same example we used in the prop-drilling example but now using the useContext concept.
import React, { createContext } from 'react'
export const MyContext = createContext();
Now, this is the first stage of the solution of our code. Next, we will wrap our App component with the context we just created.
import { useContext, useState } from 'react'
import './App.css'
import Dashboard from './Dashboard'
import { MyContext } from './Context'
function App() {
const [sex, setSex] = useState('male')
const [name, setName] = useState('Louisa')
const [activePlayer, setActivePlayer] = useState('Yes')
return (
<div className="App">
<MyContext.Provider value={{
sex, setSex, name, setName, activePlayer, setActivePlayer
}}>
<h3> Welcome {name}</h3>
<Dashboard />
</MyContext.Provider>
</div>
)
}
export default App
As you can see, we wrapped the App being the parent component with our context MyContext.Provider. With this, all the components in the application have access to the value prop we set.
Now, let's see the rest of the code. You will realize they aren't as large as they were while prop-drilling. Now, they are lightweight and much more readable while still giving the same result.
import React, { useContext } from 'react'
import { MyContext } from './Context'
const PlayerState = () => {
const playerStatus = useContext(MyContext)
return (
<div className='player-state'>
<h4>Is the player active? { playerStatus.activePlayer}</h4>
</div>
)
}
export default PlayerState
import React, { useContext } from 'react'
import { MyContext } from './Context'
const PlayerSex = () => {
const playerSex = useContext(MyContext)
return (
<div>Can you guess player sex? { playerSex.sex}</div>
)
}
export default PlayerSex
import React, { useContext } from 'react'
import { MyContext } from './Context'
const PlayerState = ({ activePlayer }) => {
const playerStatus = useContext(MyContext)
return (
<div className='player-state'>
<h4>Is the player active? { playerStatus.activePlayer}</h4>
</div>
)
}
export default PlayerState
To be able to output our JSX, we first name a variable, then call our context using the _useContex_t hook. Then we can render using our variable and call on the state.
Now, these are all the components we need because we didn't have to pass the props through every component manually to get to our desired component.
If this was helpful, do well to follow me. And if you have questions, hola at me in the comment section.
Have a great weekend my friends.
Top comments (1)
What are the drawbacks of context? Can I use it as central data storage and keep mutating?