React: Context and Global State

Last updated: 2026-08-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 📖 Display only
Context-based state shared across components
JSX
// Requirements:User switches to dark mode,All child components change accordingly

// Layer 5: innermost Button needs to Know theme
function Button({ theme }) {
  return <button style={{
    backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
    color: theme === 'dark' ? 'white' : '#333'
  }}>Button</button>
}

// Layer 4
function Sidebar({ theme }) {
  return <div><Button theme={theme} /></div>
}

// Layer 3
function MainLayout({ theme }) {
  return <div><Sidebar theme={theme} /></div>
}

// Layer 2
function Page({ theme }) {
  return <MainLayout theme={theme} />
}

// Layer 1: App has theme state
function App() {
  const [theme, setTheme] = useState('light')
  return <Page theme={theme} />  // ❌ theme penetrates 5 layers
}
▶ Try it Yourself

Output:

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

Alice's question:

(2) Solution for "Context"

JSX
// 1. Create Context
const ThemeContext = React.createContext('light')

// 2. Provider Provide data
function App() {
  const [theme, setTheme] = useState('light')
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Page />
    </ThemeContext.Provider>
  )
}

// The middle component is completely unconcerned with theme
function Page() { return <MainLayout /> }
function MainLayout() { return <Sidebar /> }
function Sidebar() { return <Button /> }

// 5. Only the necessary components are loaded Context
function Button() {
  const { theme, setTheme } = useContext(ThemeContext)
  return (
    <button style={{
      backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
      color: theme === 'dark' ? 'white' : '#333'
    }}
    onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
      {theme === 'dark' ? '🌙' : '☀️'} Switch Themes
    </button>
  )
}
▶ 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
JSX
// 1. Create Context(You can specify a default value)
const UserContext = createContext({ name: 'Guest', role: 'guest' })
const ThemeContext = createContext('light')

// 2. Provider Provide data(The component tree that the wrapper needs to access)
function App() {
  const [user, setUser] = useState({ name: 'Alice', role: 'admin' })
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <ThemeContext.Provider value="dark">
        <Dashboard />
      </ThemeContext.Provider>
    </UserContext.Provider>
  )
}

// 3. Any child component reads Context
function Dashboard() {
  const { user } = useContext(UserContext)
  const theme = useContext(ThemeContext)
  return <p>{user.name} - Topic:{theme}</p>
}
▶ 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

JSX
// ============================================
// Example:Custom ThemeProvider + useTheme Hook
// ============================================

const ThemeContext = React.createContext(null)

function ThemeProvider({ children }) {
  const [theme, setTheme] = React.useState(() => {
    // from  localStorage Read the initial topic
    return localStorage.getItem('theme') || 'light'
  })

  function toggleTheme() {
    setTheme(prev => {
      const next = prev === 'light' ? 'dark' : 'light'
      localStorage.setItem('theme', next)  // Persistence
      return next
    })
  }

  const value = React.useMemo(
    () => ({ theme, toggleTheme }),
    [theme]
  )

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  )
}

// Custom Hook:Packaging useContext + Error Checking
function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme Must be ThemeProvider For internal use only')
  }
  return context
}

// Usage
function ThemedButton() {
  const { theme, toggleTheme } = useTheme()
  return (
    <button onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
        color: theme === 'dark' ? 'white' : '#333',
        padding: '10px 20px',
        border: 'none',
        borderRadius: '4px',
        cursor: 'pointer'
      }}>
      Current Topic:{theme === 'dark' ? '🌙' : '☀️'} Click to toggle
    </button>
  )
}


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:

JSX
// ============================================
// Example:Global Notification System(Context + useReducer)
// ============================================

// ---- 1. Create Context ----
const NotificationContext = createContext(null)

// ---- 2. Definition reducer ----
function notificationReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, {
        id: Date.now(),
        message: action.message,
        type: action.notifType || 'info',
        read: false
      }]
    case 'MARK_READ':
      return state.map(n =>
        n.id === action.id ? { ...n, read: true } : n
      )
    case 'REMOVE':
      return state.filter(n => n.id !== action.id)
    case 'CLEAR_ALL':
      return []
    default:
      return state
  }
}

// ---- 3. Provider Components ----
function NotificationProvider({ children }) {
  const [notifications, dispatch] = useReducer(notificationReducer, [])

  // use  useMemo Stable value
  const value = useMemo(() => ({ notifications, dispatch }), [notifications])

  return (
    <NotificationContext.Provider value={value}>
      {children}
    </NotificationContext.Provider>
  )
}

// ---- 4. Custom Hook ----
function useNotification() {
  const context = useContext(NotificationContext)
  if (!context) throw new Error('useNotification Must be NotificationProvider For internal use')
  return context
}

// ---- 5. Using the notification component ----
function NotificationBell() {
  const { notifications, dispatch } = useNotification()
  const unread = notifications.filter(n => !n.read).length

  return (
    <div style={{ position: 'relative', cursor: 'pointer' }}>
      🔔
      {unread > 0 && (
        <span style={{
          position: 'absolute', top: '-8px', right: '-8px',
          backgroundColor: '#ff4d4f', color: 'white',
          borderRadius: '50%', width: '18px', height: '18px',
          fontSize: '12px', display: 'flex', alignItems: 'center',
          justifyContent: 'center'
        }}>
          {unread}
        </span>
      )}
    </div>
  )
}

function NotificationList() {
  const { notifications, dispatch } = useNotification()

  if (notifications.length === 0) {
    return <p style={{ color: '#999', textAlign: 'center' }}>No notice at this time</p>
  }

  return (
    <div style={{ maxWidth: '400px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
        <strong>Notice({notifications.length})</strong>
        <button onClick={() => dispatch({ type: 'CLEAR_ALL' })} style={smallBtn}>Clear All</button>
      </div>
      {notifications.map(n => (
        <div key={n.id} style={{
          padding: '8px', marginBottom: '4px',
          backgroundColor: n.read ? '#fafafa' : '#e6f7ff',
          borderRadius: '4px', cursor: 'pointer',
          display: 'flex', justifyContent: 'space-between',
          alignItems: 'center'
        }}
        onClick={() => dispatch({ type: 'MARK_READ', id: n.id })}>
          <span>
            {n.type === 'error' ? '❌' : n.type === 'success' ? '✅' : 'ℹ️'}
            {n.message}
          </span>
          <button onClick={e => { e.stopPropagation(); dispatch({ type: 'REMOVE', id: n.id }) }}
            style={{ ...smallBtn, color: '#ff4d4f' }}>✕</button>
        </div>
      ))}
    </div>
  )
}

// ---- The component that sends notifications ----
function NotificationSender() {
  const { dispatch } = useNotification()
  const [message, setMessage] = useState('')

  function send(type) {
    if (!message.trim()) return
    dispatch({ type: 'ADD', message: message.trim(), notifType: type })
    setMessage('')
  }

  return (
    <div>
      <input value={message} onChange={e => setMessage(e.target.value)}
        placeholder="Notice Content" style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
      <div style={{ display: 'flex', gap: '4px' }}>
        <button onClick={() => send('info')} style={typeBtn('#1890ff')}>ℹ️ info</button>
        <button onClick={() => send('success')} style={typeBtn('#52c41a')}>✅ Success</button>
        <button onClick={() => send('error')} style={typeBtn('#ff4d4f')}>❌ Error</button>
      </div>
    </div>
  )
}

const smallBtn = { padding: '2px 8px', fontSize: '12px', border: 'none', backgroundColor: 'transparent', cursor: 'pointer' }
const typeBtn = (color) => ({ padding: '6px 12px', backgroundColor: color, color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' })

// ---- 6. Assembly ----
function NotificationApp() {
  return (
    <NotificationProvider>
      <div style={{ maxWidth: '500px', margin: '0 auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <h2>🔔 Notification System</h2>
          <NotificationBell />
        </div>
        <NotificationSender />
        <hr style={{ margin: '16px 0' }} />
        <h3>List of Notices</h3>
        <NotificationList />
      </div>
    </NotificationProvider>
  )
}

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)

Output:

TEXT 📖 Display only
Displays: "Button". State: theme (setter: setTheme). Button: Button
JSX
// ============================================
// Example:User Authentication System——Context + Custom Hook Best Practices
// Features:Admin Login/Logged Out,Provide user information globally
// ============================================

import { createContext, useContext, useState, useCallback, useMemo } from 'react'

// ---- 1. Create Context ----
const AuthContext = createContext(null)

// ---- 2. Provider Components ----
function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  const login = useCallback(async (username, password) => {
    setLoading(true)
    try {
      // Simulate a login request
      await new Promise(resolve => setTimeout(resolve, 800))
      if (username && password) {
        const loggedInUser = {
          id: 1,
          name: username,
          email: `${username}@example.com`,
          avatar: 'https://i.pravatar.cc/40',
          role: username === 'admin' ? 'Administrator' : 'Regular User'
        }
        setUser(loggedInUser)
        localStorage.setItem('user', JSON.stringify(loggedInUser))
        return { success: true }
      }
      return { success: false, error: 'Incorrect username or password' }
    } finally {
      setLoading(false)
    }
  }, [])

  const logout = useCallback(() => {
    setUser(null)
    localStorage.removeItem('user')
  }, [])

  // use  useMemo Stable value Quote,Avoid Provider Render all child components again
  const value = useMemo(() => ({
    user,
    loading,
    isAuthenticated: !!user,
    login,
    logout
  }), [user, loading, login, logout])

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}

// ---- 3. Custom Hook + Error Checking ----
function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth Must be AuthProvider For internal use only')
  }
  return context
}

// ---- 4. Use Certified Components ----
function LoginForm() {
  const { login, loading } = useAuth()
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState('')

  async function handleSubmit(e) {
    e.preventDefault()
    setError('')
    const result = await login(username, password)
    if (!result.success) {
      setError(result.error)
    }
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px', margin: '0 auto' }}>
      <h2>🔐 Log In</h2>
      {error && <p style={{ color: '#ff4d4f', fontSize: '14px' }}>{error}</p>}
      <input value={username} onChange={e => setUsername(e.target.value)}
        placeholder="Username(Input admin Experience the Role of an Administrator)"
        style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)}
        placeholder="Password"
        style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
      <button type="submit" disabled={loading}
        style={{ width: '100%', padding: '10px', backgroundColor: '#1890ff', color: 'white', border: 'none', borderRadius: '4px', cursor: loading ? 'not-allowed' : 'pointer' }}>
        {loading ? 'Logging in......' : 'Log In'}
      </button>
    </form>
  )
}

function UserDashboard() {
  const { user, logout } = useAuth()

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
        <img src={user.avatar} alt={user.name}
          style={{ width: '40px', height: '40px', borderRadius: '50%' }} />
        <div>
          <h3 style={{ margin: 0 }}>{user.name}</h3>
          <p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '13px' }}>
            {user.email} · {user.role}
          </p>
        </div>
      </div>
      <button onClick={logout}
        style={{ padding: '8px 16px', backgroundColor: '#ff4d4f', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
        Log Out
      </button>
    </div>
  )
}

// ---- 5. Top-level component ----
function AuthApp() {
  const { isAuthenticated } = useAuth()
  return isAuthenticated ? <UserDashboard /> : <LoginForm />
}

// ---- 6. Entrance ----
function App() {
  return (
    <AuthProvider>
      <div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
        <AuthApp />
      </div>
    </AuthProvider>
  )
}

Output:

TEXT 📖 Display only
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})

▶ Example 3: Combining Multiple Provider Layers with i18n Internationalization

Output:

TEXT 📖 Display only
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
JSX
const I18nContext = createContext(null)

function I18nProvider({ children }) {
  const [locale, setLocale] = useState('zh')
  const translations = {
    zh: { title: 'App Title', greeting: 'Hello', logout: 'Exit' },
    en: { title: 'App Title', greeting: 'Hello', logout: 'Logout' },
  }
  const t = useCallback((key) => translations[locale][key] || key, [locale])
  const value = useMemo(() => ({ locale, setLocale, t }), [locale, t])
  return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
}

function useI18n() {
  const ctx = useContext(I18nContext)
  if (!ctx) throw new Error('useI18n must be inside I18nProvider')
  return ctx
}

function LocaleSwitcher() {
  const { locale, setLocale } = useI18n()
  return (
    <select value={locale} onChange={e => setLocale(e.target.value)}
      style={{ padding: '4px 8px', borderRadius: 4 }}>
      <option value="zh">Chinese</option>
      <option value="en">English</option>
    </select>
  )
}

function Greeting() {
  const { t } = useI18n()
  return <h2>{t('greeting')}</h2>
}

function I18nApp() {
  return (
    <I18nProvider>
      <div style={{ maxWidth: 400, margin: '0 auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
          <Greeting />
          <LocaleSwitcher />
        </div>
      </div>
    </I18nProvider>
  )
}

Output:

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

▶ Example 4: Splitting the Context to Optimize Performance

Output:

TEXT 📖 Display only
Subheading: "{t('greeting')}". Displays: "Chinese". State: locale (setter: setLocale). Dropdown: Chinese, English. useMemo optimizes computation. useCallback memoizes handler. Context provides global state
JSX
const UserStateContext = createContext(null)
const UserDispatchContext = createContext(null)

function UserProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  const dispatch = useMemo(() => ({
    login: async (name) => {
      setLoading(true)
      await new Promise(r => setTimeout(r, 500))
      setUser({ name, role: name === 'admin' ? 'Admin' : 'User' })
      setLoading(false)
    },
    logout: () => setUser(null),
  }), [])

  const stateValue = useMemo(() => ({ user, loading }), [user, loading])

  return (
    <UserDispatchContext.Provider value={dispatch}>
      <UserStateContext.Provider value={stateValue}>
        {children}
      </UserStateContext.Provider>
    </UserDispatchContext.Provider>
  )
}

function useUserState() { return useContext(UserStateContext) }
function useUserDispatch() { return useContext(UserDispatchContext) }

function LoginButton() {
  const { login } = useUserDispatch()
  const { loading } = useUserState()
  return <button disabled={loading} onClick={() => login('Alice')}>Login</button>
}

function UserInfo() {
  const { user } = useUserState()
  if (!user) return <p>Not logged in</p>
  return <p>Logged in as {user.name} ({user.role})</p>
}

function SplitContextApp() {
  return (
    <UserProvider>
      <div style={{ maxWidth: 400, margin: '0 auto' }}>
        <UserInfo />
        <LoginButton />
      </div>
    </UserProvider>
  )
}

Output:

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

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

Output:

TEXT 📖 Display only
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
JSX
const SettingsContext = createContext(null)

function SettingsProvider({ children }) {
  const [settings, setSettings] = useState({
    theme: 'light', fontSize: 14, sidebarCollapsed: false,
  })

  const updateSetting = useCallback((key, value) => {
    setSettings(prev => ({ ...prev, [key]: value }))
  }, [])

  const value = useMemo(() => ({ settings, updateSetting }), [settings, updateSetting])

  return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>
}

function useSettings() {
  const ctx = useContext(SettingsContext)
  if (!ctx) throw new Error('useSettings must be inside SettingsProvider')
  return ctx
}

function Toolbar() {
  const { settings, updateSetting } = useSettings()
  return (
    <div style={{ padding: 8, background: settings.theme === 'dark' ? '#333' : '#f0f0f0', display: 'flex', gap: 8, alignItems: 'center' }}>
      <button onClick={() => updateSetting('theme', settings.theme === 'dark' ? 'light' : 'dark')}>
        {settings.theme === 'dark' ? '☀️' : '🌙'}
      </button>
      <button onClick={() => updateSetting('sidebarCollapsed', !settings.sidebarCollapsed)}>
        {settings.sidebarCollapsed ? '▶' : '◀'} Sidebar
      </button>
      <select value={settings.fontSize} onChange={e => updateSetting('fontSize', Number(e.target.value))}>
        <option value={12}>12px</option>
        <option value={14}>14px</option>
        <option value={16}>16px</option>
        <option value={18}>18px</option>
      </select>
    </div>
  )
}

function Content() {
  const { settings } = useSettings()
  return (
    <div style={{ fontSize: settings.fontSize, color: settings.theme === 'dark' ? '#eee' : '#333',
      background: settings.theme === 'dark' ? '#1a1a1a' : '#fff', padding: 16 }}>
      <p>Current settings:</p>
      <ul>
        <li>Theme: {settings.theme}</li>
        <li>Font size: {settings.fontSize}px</li>
        <li>Sidebar: {settings.sidebarCollapsed ? 'collapsed' : 'expanded'}</li>
      </ul>
    </div>
  )
}

function SettingsApp() {
  return (
    <SettingsProvider>
      <div style={{ maxWidth: 600, margin: '0 auto' }}>
        <Toolbar />
        <Content />
      </div>
    </SettingsProvider>
  )
}

Output:

TEXT 📖 Display only
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%

🙏 帮我们做得更好

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

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