React Context API is a feature that was introduced since React v.16.3, this feature give us as a React Developer a superpower to pass data through our components trees, which can be really useful for many scenarios. ✨
Before we dive in into how to use React Context, let's first we learn on why this feature is created at the first place. React application is usually consists of multiple components with a parent and children relationships. That means, the data from parent component will be passed to the child component using "props".
Unidirectional data flow is one of main concept of react, in nutshell changing state on a component will never affect its parent or its siblings, only the children will be affected.
This pattern will give us more confident in our code, because our code will be easier to be debugged and less prone to error. However, if there is any data from children component that needed to be shared between components, then the state needed to be lifted up into nearest parent component.
However in a big application this can be get messy very quickly. Imagine we need user token to be passed out to our deep nested component to call some API.
const { useState, useEffect } from 'react'; const { getToken, getData } from './API/auth'; const App = () => { const [token, setToken] = useState(null); useEffect(() => { const userAuth = async () => { const _token = await getToken(); setToken(_token) } }, []) return <Dashboard token={token} /> } const Dashboard = ({token}) => { return ( <div> <h1>My Dashboard</h1> <UserSummary token={token} /> </div> ) } const UserSummary = ({token}) => { const [userData, setUserData] = useState(null); useEffect(() => { const fetchUserData = async (t) => { const _userData = await getData(t); setUserData(_userData); } if (token) fetchUserData(token) }, [token]) return( { !userData ? <h1>Loading...</h1> : <h1>{JSON.stringify(userData, null, 2)}</h1> } ); }
Let's take a look at the contrived code above. UserSummary
component need a token to fetch user data. However to do that we need token that has been generated from App
component. We pass token as prop into Dashboard
and then pass it again into UserSummary
component.
From the example above we know Dashboard
component really did not need token
prop. However because UserSummary
is the children from Dashboard
component, therefore we must pass token
via Dashboard
. It may be look okay-ish if there are only one or two level of nested component. However in real world scenario, we may need token
for many components in our app that might need it. Are we going to pass the token
prop all around the places? This problem is known as prop drilling 🛠
By using React Context, we are able to pass data through the component tree from parent to child components, without having to pass props down manually at each level.
There are four steps on how to using React Context:
Create context using createContext()
.
Wrap the component using context provider from the created context before.
Set the default value using value
property on context provider.
Get the value of context using useContext
hooks.
Let's convert the example above to using React Context.
const { useState, useEffect, createContext, useContext } from 'react'; const { getToken, getData } from './API/auth'; export const TokenContext = React.createContext(); const App = () => { const [token, setToken] = useState(null); useEffect(() => { const userAuth = async () => { const _token = await getToken(); setToken(_token) } }, []) return ( <TokenContext.Provider value={token}> <Dashboard /> </TokenContext.Provider> ) } const Dashboard = ({token}) => { return ( <div> <h1>My Dashboard</h1> <UserSummary /> </div> ) } const UserSummary = ({token}) => { const value = React.useContext(TokenContext); const [userData, setUserData] = useState(null); useEffect(() => { const fetchUserData = async (t) => { const _userData = await getData(t); setUserData(_userData); } if (token) fetchUserData(token) }, [token]) return( { !userData ? <h1>Loading...</h1> : <h1>{JSON.stringify(userData, null, 2)}</h1> } ); }
In the example above, we create a new context and store it as TokenContext
variable. We wrap App
component using Provider
from TokenContext
, and provide the initial value which is in our case is the token
. Finally using useContext()
hooks, we get the context value (which is token
) in our UserSummary
component.
By using React Context, we make our code ma lot more concise. Which also eliminates the props drilling problem. Aside from React context, there are also another options available like Redux or MobX which is a global state management.
As a rule of thumb, if there are not a lot amount of state that needed to be managed globally, and the state not updated frequently we should use React Context instead of Redux. However if we are managing a large amount of state and updated frequently we might to consider to use Redux.
I hope this short article help you in you journey on learning React 🍻
Discuss on Twitter