React: Context and Global State

Last updated: 2026-10-26

1. What You'll Learn



2. A Story About Changing Themes

(1) Pain Point: Props Passing Through 5 Levels of Components

Alice wants to add a "dark mode" toggle feature to the app. The data flow must pass through five layers of components:

▶ Example: Using Context

Output:

TEXT
Context-based state shared across components
▶ Try it Yourself

Output:

TEXT
App component with state: 'light'. Renders interactive UI.

Alice's question:

(2) Solution for "Context"

▶ Try it Yourself

Benefits: Intermediate components (Page, MainLayout, Sidebar) no longer need to pass theme props; data is passed directly from the Provider to the Consumer.



3. Core Concepts of Context

API Function Parameters/Return Values
createContext(defaultValue) Create a context container Back to { Provider, Consumer }
<Context.Provider value={...}> Provides data to the subtree value: Any type; triggers a Consumer re-render when it changes
useContext(Context) Retrieve the most recent Provider data Return the Provider's value; if no Provider is found, return the default value
Custom useXxx() Hook Wraps useContext + error checking Recommended pattern: Throws a clear error when Provider is missing

(1) createContext + Provider + useContext

100%
graph TB
    A[createContext<br/>Create a context container] --> B[Provider<br/>Wrapper Component tree<br/>Provide value]
    B --> C[Middle Component<br/>Don't care Context]
    C --> D[useContext<br/>Consumption Context Data]
    
    style A fill:#1890ff,color:#fff
    style B fill:#52c41a,color:#fff
    style D fill:#ff6b6b,color:#fff
▶ Try it Yourself

(2) Scenarios in Which Context Is Applicable

Suitable for Context Not suitable for Context
Theme (Dark Mode) Component Configuration Parameters (using Props)
User Login Information Form Input Values (using local state)
Language/Internationalization API Data (using TanStack Query)
Route State Animation State (Managed by the animation library)
Global Settings Simple Parent-to-Child Prop Passing (Using Props Directly)

Rule: Use Context only when data needs to be shared across "multiple levels and multiple components." If you're simply passing values between parent and child components, Props are sufficient.



4. Context Nesting and Provider Componentization

(1) Multi-level Context Nesting

Real-world projects typically have multiple contexts:

JSX
function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <I18nProvider>
          <NotificationProvider>
            <MainApp />
          </NotificationProvider>
        </I18nProvider>
      </ThemeProvider>
    </AuthProvider>
  )
}
▶ Try it Yourself

To avoid nesting hell, you can create a AppProviders composite component:

JSX
function AppProviders({ children }) {
  return (
    <AuthProvider>
      <ThemeProvider>
        <I18nProvider>
          <NotificationProvider>
            {children}
          </NotificationProvider>
        </I18nProvider>
      </ThemeProvider>
    </AuthProvider>
  )
}

function App() {
  return (
    <AppProviders>
      <MainApp />
    </AppProviders>
  )
}
▶ Try it Yourself

(2) Custom Provider Hook

▶ Try it Yourself

5. Context Performance Optimization

When the value of a Context changes, all components that consume that Context will re-render. This is the biggest performance pitfall of Contexts.

Optimization Method Principle Applicable Scenarios
useMemo Stable value Avoid creating new object references on every render When a Provider's value contains a function or an object
Split by Context "Changing" and "unchanging" data are assigned to different Contexts Some Consumers only need stable data
React.memo Wrapping Consumer Skipping re-rendering when props haven't changed When the Consumer component receives other props
Selective Subscription Use a selector to retrieve only the slices you need Implement precise updates with Zustand/Redux
JSX
// ❌ Question:Every time App Rendering,value They're all new partners. → All Consumer All are heavily rendered
function App() {
  const [count, setCount] = useState(0)
  return (
    <ThemeContext.Provider value={{ theme: 'dark', setCount }}>
      <ExpensiveTree />
    </ThemeContext.Provider>
  )
}

// ✅ Fix 1:use  useMemo Stable value Quote
function App() {
  const [count, setCount] = useState(0)
  const themeValue = useMemo(
    () => ({ theme: 'dark', toggleTheme: () => setCount(c => c + 1) }),
    []
  )
  return (
    <ThemeContext.Provider value={themeValue}>
      <ExpensiveTree />
    </ThemeContext.Provider>
  )
}

// ✅ Fix 2:separate "unchanged" and "changed" data into different Context
const SettingsContext = createContext({ theme: 'light', language: 'zh' })
const CountContext = createContext(0)

function App() {
  const [count, setCount] = useState(0)
  return (
    <SettingsContext.Provider value={settingsValue}> {/* useMemo Stable */}
      <CountContext.Provider value={count}>
        <ExpensiveTree />
      </CountContext.Provider>
    </SettingsContext.Provider>
  )
}
// Only when used CountContext The component will be count Re-render on changes
// SettingsContext Consumers will not be affected
▶ Try it Yourself

6. Context + useReducer: Building Lightweight Global State

By combining Context's "cross-level propagation" with useReducer's "complex state management," you can build a lightweight global state solution:

▶ Expand runnable example (125 lines)
▶ Try it Yourself

Output:

TEXT
Displays: "Button". State: theme (setter: setTheme). Button: Button

Expected Output: A complete notification system. A notification bell (displaying the number of unread notifications) appears at the top; in the middle, users can send three types of notifications: info, success, and error; and at the bottom, a list of notifications is displayed, where each notification can be marked as read or deleted.


▶ Example 2: User Authentication Status Management (Context + Custom Hook)

▶ Expand runnable example (114 lines)
▶ Try it Yourself

Output:

TEXT
Login form with email/password. Invalid email → red border + "The email address format is incorrect." Password <6 → "Password must be at least 6 chars". Submit → console.log({email, password})

Output:

TEXT
Subheading: "🔐 Log In". Displays: "🔐 Log In". State: user (setter: setUser), loading (setter: setLoading), username (setter: setUsername). Buttons: {loading ? 'Logging in......' : 'Log In'}, Log Out. Input: Username(Input admin Experience the Role of an Administrator), Password. Form with submit handling. useMemo optimizes computation. useCallback memoizes handler. Context provides global state. Async data fetching/loading states. Timer-based behavior

▶ Example 3: Combining Multiple Provider Layers with i18n Internationalization

▶ Try it Yourself

Output:

TEXT
Internationalization: language switcher changes all text. Context + provider pattern for multi-language support

Output:

TEXT
Subheading: "{t('greeting')}". Displays: "Chinese". State: locale (setter: setLocale). Dropdown: Chinese, English. useMemo optimizes computation. useCallback memoizes handler. Context provides global state

▶ Example 4: Splitting the Context to Optimize Performance

▶ Try it Yourself

Output:

TEXT
SplitContextApp component with state: null, false. Renders interactive UI.

Output:

TEXT
Displays: "Login". State: user (setter: setUser), loading (setter: setLoading). Button: login('Alice')}>Login. useMemo optimizes computation. Context provides global state. Async data fetching/loading states. Timer-based behavior

▶ Example 5: Complete Example of Using useContext as an Alternative to Props Drilling

▶ Try it Yourself

Output:

TEXT
SettingsApp component with state: {
    theme: 'light', fontSize: 14, sidebarCollapsed: false,
  }. Renders interactive UI.

❓ FAQ

Q What should I do if the Context's value is a new object every time, causing all Consumers to re-render?
A Use useMemo to stabilize the value reference: const value = useMemo(() => ({ theme, toggleTheme }), [theme]). You can also separate "stable" and "changing" data into different Contexts. If performance issues persist after optimization, consider using Zustand (which supports precise updates and won't trigger the re-rendering of unrelated components).
Q What should I do if there are too many layers of nested Contexts?
A ① Use a Provider to group components (such as the AppProviders mentioned earlier) and encapsulate the nesting; ② Consider whether you really need that many Contexts—some "global" data is actually just "locally global" (such as settings for a specific page) and doesn't need to be placed in the top-level Context of the App.
Q Why do we check if context is null in a custom useXxx hook?
A This is a defensive programming technique. If a developer forgets to wrap a component with a Provider before using useContext, context will be null (if no default value was passed to createContext) or cause errors that are difficult to debug. Throwing a clear error message early on is much more user-friendly than having the user see “Cannot read property of null” in the browser console.
Q Can Context replace Redux?
A It can for small to medium-sized projects, but not for large ones. Context lacks three capabilities that Redux has: ① the middleware mechanism (logging, asynchronous operations, persistence); ② time-travel debugging (replaying state changes in DevTools); ③ Selective subscription (a change in a Context value causes all Consumers to re-render, whereas Redux allows you to use selectors to subscribe only to the specific slices you need). If your project requires these three capabilities, use Zustand (lightweight) or Redux Toolkit.

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Create a LocaleProvider + useLocale hook that provides the locale (zh/en) and toggleLocale() methods to enable switching between Chinese and English.
  2. Advanced Exercise (Difficulty ⭐⭐): Create a AuthProvider + useAuth hook that provides the user, login(username, password), and logout() methods to manage the user’s authentication status.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a CartProvider + useCart hook and use Context + useReducer to implement a shopping cart with functions to add, remove, change quantities, and clear the cart. Use useMemo to optimize the code and avoid unnecessary re-rendering.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏