HomeAbout

Context API

What is it

Context API is a way to pass data through the component tree without having to manually pass props down at every level.

  • Not having to pass the 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);

What is it NOT

Context is NOT for state management

Context is called every time its parent component re-renders.

  • If any part of context value provided to the Provider is different, it will cause a re-render.

Use Redux, Recoil, Zustand for frequent state management.

Alternative

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> ); };

Architecture

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.

  • This function returns a context object.
import { createContext } from "react"; export const ThemeContext = createContext({ theme: "dark", setTheme: (theme: string) => {}, });

Context Value

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.

  • unless higher level context directly needs values from the lower level context.

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.

Update and Re-render Rule

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.

  • You will only get the initial value that you set only in the first render.

Update

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.

  • Current context value is determined by the value props of the nearest <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); // ... }
AboutContact