ref – https://www.taniarascia.com/using-context-api-in-react/
https://www.toptal.com/react/react-context-api
React Context solves one major problem: prop drilling.
Prop drilling is the processing of getting data from component A to component Z by passing it through multiple layers of intermediary React components. Component will receive props indirectly and we have to ensure all the data gets the latest update.
So if a child component changes data, then we have to make sure its grand children and parents gets this updated data. This can lead to too much data tracking work.
In a few words, the Context API allows you to have a central store where your data lives (yes, just like in Redux). The store can be inserted into any component directly.
Provider – The component that provides the value
Consumer – A component that is consuming the value
Update 2023
Context is a place where we create all the data that we want our children to be able to use.
First we create a context with initial data by using react’s createContext.
1 2 3 4 |
import { createContext, useEffect } from "react"; // create our context const SettingsContext = createContext(initialState); |
Now, we use this SettingsContext so that we can access its Provider property like this SettingsContext.Provider
This is so that we can give data to its value. This value is kept so that children components can access them.
1 2 3 4 5 6 7 8 9 10 |
return ( <SettingsContext.Provider value={{ onToggleMode, onChangeMode, // other handlers }}> {children} </SettingsContext.Provider> ); |
We do all of the mentioned in a functional component like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const SettingsProvider = ({ children }) => { const [settings, setSettings] = useLocalStorage("settings", { themeMode: initialState.themeMode, themeLayout: initialState.themeLayout, themeStretch: initialState.themeStretch, themeContrast: initialState.themeContrast, themeDirection: initialState.themeDirection, themeColorPresets: initialState.themeColorPresets, }); const onToggleMode = () => {...} const onChangeMode = (event) => {...} // other handlers // then we insert our data, handlers and whatever we want children components to access // in the value of our SettingsContext.Provider return ( <SettingsContext.Provider value={{ onToggleMode, onChangeMode, // other handlers }}> {children} </SettingsContext.Provider> ); }; export {SettingsContext}; export default SettingsProvider; |
And so we use it in our index file like so:
src/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// contexts import SettingsProvider from "./contexts/SettingsContext"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <SettingsProvider> <BrowserRouter> <App /> </BrowserRouter> </SettingsProvider> </React.StrictMode> ); |
In order to set up usage in our children components, we get useContext, and insert our SettingsContext functional component in there, in order to return a hook.
src/hooks/useSettings.js
1 2 3 4 5 6 7 |
import { useContext } from 'react'; import {SettingsContext} from '../contexts/SettingsContext'; // create hook const useSettings = () => useContext(SettingsContext); export default useSettings; |
1 2 3 |
import useSettings from "../hooks/useSettings.js"; const { themeMode, themeDirection } = useSettings(); |
2020
Creating the Context
First, we need to create the context, which we can later use to create providers and consumers.
src/UserContext.js
1 2 3 4 5 6 7 8 |
import React from 'react' const UserContext = React.createContext() export const UserProvider = UserContext.Provider export const UserConsumer = UserContext.Consumer export default UserContext |
Providing Context
The provider always needs to exist as a wrapper around the parent element, no matter how you choose to consume the values. I’ll wrap the entire App component in the Provider. I’m just creating some value (user) and passing it down as the Provider value prop.
src/App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import React from 'react' import HomePage from './HomePage' import { UserProvider } from './UserContext' function App() { const user = { name: 'Tania', loggedIn: true } return ( <UserProvider value={user}> <HomePage /> </UserProvider> ) } |
Now any child, grandchild, great-grandchild, and so on will have access to user as a prop. Unfortunately, retrieving that value is slightly more involved than simply getting it like you might with this.props or this.state.
Consuming Context
The way you provide Context is the same for class and functional components, but consuming it is a little different for both.
Class component way
The most common way to access Context from a class component is via the static contextType. If you need the value from Context outside of render, or in a lifecycle method, you’ll use it this way.
src/HomePage.js (class example)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import React, { Component } from 'react' import UserContext from './UserContext' class HomePage extends Component { static contextType = UserContext componentDidMount() { const user = this.context console.log(user) // { name: 'Tania', loggedIn: true } } render() { return <div>{user.name}</div> } } |
Functional component and Hooks
For functional components, you’ll use useContext, such as in the example below. This is the equivalent of static contextType.
src/HomePage.js
1 2 3 4 5 6 7 8 9 |
import React, { useContext } from 'react' import UserContext from './UserContext' const HomePage = () => { const user = useContext(UserContext) return <div>{user.name}</div> } export default HomePage; |
Updating Context
Updating context is not much different than updating regular state. We can create a wrapper class that contains the state of Context and the means to update it.
We create a Provider that connects a getter/setter to the UserContext.
In other components, these methods are then extracted from UserContext, and used to change the context value.
src/UserContext.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import React, { Component } from 'react' const UserContext = React.createContext() class UserProvider extends Component { // Context state state = { user: {}, } // Method to update state setUser = (user) => { this.setState((prevState) => ({ user })) } render() { const { children } = this.props const { user } = this.state const { setUser } = this return ( <UserContext.Provider value={{ user, setUser, }} > {children} </UserContext.Provider> ) } } export default UserContext export { UserProvider } |
Let’s create a component, use this UserProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import React, { Component } from 'react' import UserContext from './UserContext' class HomePage extends Component { static contextType = UserContext render() { const { user, setUser } = this.context return ( <div> <button onClick={() => { const newUser = { name: 'Joe', loggedIn: true } setUser(newUser) }} > Update User </button> <p>{`Current User: ${user.name}`}</p> </div> ) } } |