Context API is a way to pass data through the component tree without having to manually pass props down at every level.
props
from top level each level down to the level where it is consumed.Context
is typically an object that holds the values that get consumed throughout the app
// context creation import { createContext } from 'react'; const ContextName = createContext( {...} ); // context consumption import { useContext } from 'react'; const { user } = useContext(ContextName);
Context is called every time its parent component re-renders.
Provider
is different, it will cause a re-render.Use Redux, Recoil, Zustand for frequent state management.
Combining context with localStorage
to temporarily store contextual data is a good alternative.
import { useState, useEffect } from 'react'; import { createContext, useContext } from 'react'; const MyContext = createContext(); const MyProvider = ({ children }) => { const [user, setUser] = useState(() => { const savedUser = localStorage.getItem('user'); return savedUser ? JSON.parse(savedUser) : null; }); useEffect(() => { localStorage.setItem('user', JSON.stringify(user)); }, [user]); return ( <MyContext.Provider value={{ user, setUser }}> {children} </MyContext.Provider> ); };
Place the Provider at the root level component of the application:
import React, { createContext, useState, useContext } from 'react'; // create Context const MyContext = createContext(); // App.tsx component should hold Provider const App = () => { const [user, setUser] = useState({ name: 'Alice' }); return ( <MyContext.Provider value={user}> <ChildComponent /> </MyContext.Provider> ); }; // Child component that consumes the context const ChildComponent = () => { const user = useContext(MyContext); // Accessing context value return <div>Hello, {user.name}!</div>; }; export default App;
createContext
Context is created using the createContext
function.
context
object.import { createContext } from "react"; export const ThemeContext = createContext({ theme: "dark", setTheme: (theme: string) => {}, });
If you assign a non-primitive value to the reference (e.g. object), then all consumers will re-render every time even if the contents are the same.
// un-memoized value <MyContext.Provider value={{ count: state.count }}> <ChildComponent /> </MyContext.Provider> // memoize value const contextValue = useMemo(() => ({ count: state.count }), [state.count]); <MyContext.Provider value={contextValue}> <ChildComponent /> </MyContext.Provider>
Comparison of contextValue
is done by using Object.is()
which is same as using ===
for comparison.
const foo = { a: 1 }; const bar = { a: 1 }; const sameFoo = foo; Object.is(foo, foo); // true Object.is(foo, bar); // false Object.is(foo, sameFoo); // true
Provider
The Provider
accepts a value
prop, which determines what data will be shared across the component tree.
All descendant components that consume the context
will have access to this value
.
The order of the context Provider does not matter.
When you need value from any part of the Context hierarchy in consumption, you would just call the useContext
.
import { createContext } from "react"; export const ThemeContext = createContext({ theme: "dark", setTheme: (theme: string) => {}, }); function App() { const [theme, setTheme] = useState('light'); // ... return ( <ThemeContext.Provider value={theme}> <Page /> </ThemeContext.Provider> ); }
If a component is not wrapped in the Context.Provider
it will receive the default value that was set when the context
was created.
Provider
's job is to override the default values, essentially providing dependency injection mechanism to React component tree.
All consumers that are descendants of a Provider
will re-render whenever the Provider
’s value
prop changes.
If you don't wrap your components with Context.Provider
they won't get re-rendered when a value in createContext
changes.
initial value
that you set only in the first render.To update the context from the child component, you need to pass a function that updates the state to the context value.
import React, { useState } from 'react'; import { MyContext } from './MyContext'; export const App = ({ children }) => { const [count, setCount] = useState(0); // method to update the state const increment = () => setCount(prev => prev + 1); return ( // pass both the state and the update method // memoize or use callback when passing object <MyContext.Provider value={{count, increment}}> {children} </MyContext.Provider> ); }; // child component import { useContext } from 'react'; import { MyContext } from './MyContext'; const Counter = () => { const { count, increment } = useContext(MyContext); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); };
useContext
Accepts a Context
object and returns the current context value of that context.
<Context.Provider>
above the calling component in the tree.// create Context import { createContext } from "react"; type ContextType = { theme: string; setTheme: (theme: string) => void; }; export const ThemeContext = createContext<ContextType | undefined>(undefined); // state and provider component (level above) import { PropsWithChildren, useState } from "react"; export const ThemeProvider = ({ children }: PropsWithChildren<{}>) => { const [theme, setTheme] = useState(); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); }; // context consumption (level below) import { useContext } from 'react'; function MyComponent() { const theme = useContext(ThemeContext); // ... }