useContext is one of React's built-in hooks that allows you to consume values from React's Context API. Think of context as a way to share data that can be considered "global" for a tree of React components, without having to pass props manually through every level of the component tree.
import { createContext, useContext, useState } from"react";// 1. Create the contextconstThemeContext=createContext(null);// 2. Create a provider componentfunctionThemeProvider({ children }) {const [theme,setTheme] =useState("light");consttoggleTheme= () => {setTheme(theme ==="light"?"dark":"light"); };// The value that will be provided to consumersconstvalue= { theme, toggleTheme, };return <ThemeContext.Providervalue={value}>{children}</ThemeContext.Provider>;}// 3. Consumer component using useContextfunctionThemedButton() {// Consume the context valueconst { theme,toggleTheme } =useContext(ThemeContext);return ( <buttonstyle={{ background: theme ==="light"?"#fff":"#333", color: theme ==="light"?"#333":"#fff", border:"1px solid #ccc", padding:"8px 16px", }}onClick={toggleTheme} > Toggle Theme </button> );}// 4. Use the provider at a high level in your appfunctionApp() {return ( <ThemeProvider> <divclassName="app"> <header> <h1>My App</h1> <ThemedButton /> </header> <main>{/* Other components that can also consume ThemeContext */}</main> </div> </ThemeProvider> );}
Let's break this down:
We create a context with createContext() and provide a default value (used if no matching Provider is found)
We define a Provider component that wraps its children with the context's Provider
The Provider component includes a value prop that holds the data to be shared
Consumer components call useContext() with the context object to access the shared data
Whenever the context value changes, all consuming components re-render
In TypeScript, you can create type-safe context like this:
import { createContext, useContext, useState, ReactNode } from"react";// Define the shape of your contextinterfaceThemeContextType { theme:"light"|"dark";toggleTheme: () =>void;}// Create context with a default valueconstThemeContext=createContext<ThemeContextType|undefined>(undefined);// Provider propsinterfaceThemeProviderProps { children:ReactNode;}// Provider componentexportfunctionThemeProvider({ children }:ThemeProviderProps) {const [theme,setTheme] =useState<"light"|"dark">("light");consttoggleTheme= () => {setTheme(theme ==="light"?"dark":"light"); };return <ThemeContext.Providervalue={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;}// Custom hook for consuming the contextexportfunctionuseTheme():ThemeContextType {constcontext=useContext(ThemeContext);if (context ===undefined) {thrownewError("useTheme must be used within a ThemeProvider"); }return context;}
With this approach:
The context and provider are strongly typed
The custom hook ensures the context is used correctly
TypeScript will enforce correct usage throughout your application
While useContext is powerful, it has some important limitations:
Performance concerns: All components that use a context will re-render when the context value changes, even if they only use a small part of the context.
No built-in state management: Context is just a way to pass data down; it doesn't include state management patterns like actions, reducers, or selectors.
No optimization mechanisms: Unlike some state management libraries, Context doesn't have built-in memoization or ways to prevent unnecessary re-renders.
No middleware support: For async operations, logging, or other side effects, you need to implement these yourself.
No dev tools: There are no dedicated dev tools for debugging context changes.
While useContext is appropriate for certain types of application-wide settings (like themes, locale, or authentication), it's often not the best choice for complex global state management. Here are better alternatives:
Zustand
Zustand has become a popular alternative due to its simplicity and performance: