Favorite count: {favoriteIds.length}
React Blog Context Reducer
In this chapter, you will learn to use React Context + useReducer to manage shared data across components and pages, and implement the "Favorite Article" feature.
* * *
## Why Do We Need Global State?
So far, data is passed through Props layer by layer. However, some data needs to be accessed by multiple unrelated components:
* The favorites list needs to be shared between the home page, detail page, and navigation bar
* User login status needs to be known across all pages
* Shopping cart data needs to be accessed across pages
The pain point of Props passing (Prop Drilling): A β B β C β D, intermediate components are forced to receive Props they don't use.
Context provides a global "data channel", any descendant component can read directly, skipping the intermediate layer.
* * *
## Core Concepts of Context
| Step | Code | Purpose |
| --- | --- | --- |
| Create Context | `createContext()` | Create a shared data container |
| Provide Data | `` | Wrap at the top of the component tree, inject data |
| Consume Data | `useContext(MyContext)` | Read data in any descendant component |
* * *
## createContext and Provider
## Example
import{ createContext, useContext, useState } from 'react'
// Step 1: Create Context
const ThemeContext = createContext(null)
// Step 2: Create Provider component
function ThemeProvider({ children }){
const[theme, setTheme]= useState('light')
const toggleTheme =()=> setTheme(theme ==='light'?'dark':'light')
return(
{children}
)
}
// Step 3: Use in any component
function ThemedButton(){
const{ theme, toggleTheme }= useContext(ThemeContext)
return(
)
}
// Step 4: Wrap Provider in App
function App(){
return(
)
}
Context is like a "broadcasting tower": Provider broadcasts data outward, and useContext receives the data.
* * *
## useReducer β Managing Complex State
When state logic becomes complex (multiple sub-states, multiple update methods), useState falls short.
useReducer provides a Redux-like state management pattern: dispatch action β reducer calculates new state.
## Example
import{ useReducer } from 'react'
// reducer function: (current state, action) β new state
function favoriteReducer(state, action){
switch(action.type){
case'TOGGLE':
// If already favorited, remove it; if not, add it
if(state.includes(action.id)){
return state.filter(id => id !== action.id)
}else{
return[...state, action.id]
}
case'CLEAR':
return[]
default:
return state
}
}
function FavoriteDemo(){
// useReducer(reducer, initial value)
const[favoriteIds, dispatch]= useReducer(favoriteReducer,[])
return(
)
}
### useState vs useReducer
| Scenario | Recommendation | Reason |
| --- | --- | --- |
| Single independent state | useState | Simple and direct |
| Multiple related states | useReducer | One dispatch can update multiple states |
| Complex update logic | useReducer | Clear action types in reducer, easy to debug |
| Need to pass to deep components | useState or useReducer | Both work with Context |
* * *
## Context + useReducer Combined Pattern
This is the most powerful global state management solution in React, and it's the core idea of Redux.
## Example
// File path: src/context/FavoriteContext.jsx
import{ createContext, useContext, useReducer, useEffect } from 'react'
// 1. Create Context
const FavoriteContext = createContext(null)
// 2. Define reducer
function favoriteReducer(state, action){
switch(action.type){
case'TOGGLE':
if(state.includes(action.id)){
return state.filter(id => id !== action.id)
}else{
return[...state, action.id]
}
case'CLEAR':
return[]
default:
return state
}
}
// 3. Provider component: Combine Context + useReducer
export function FavoriteProvider({ children }){
// Restore initial state from localStorage
const[favoriteIds, dispatch]= useReducer(favoriteReducer,[],()=>{
const saved = localStorage.getItem('blog-favorites')
return saved ? JSON.parse(saved):[]
})
// When favorites list changes, automatically sync to localStorage
useEffect(()=>{
localStorage.setItem('blog-favorites', JSON.stringify(favoriteIds))
},)
// Encapsulate several common methods
function toggleFavorite(id){
dispatch({ type:'TOGGLE', id })
}
function isFavorite(id){
return favoriteIds.includes(id)
}
const value ={
favoriteIds,
favoriteCount: favoriteIds.length,
toggleFavorite,
isFavorite
}
return(
{children}
)
}
// 4. Custom Hook: Encapsulate useContext for simpler usage
export function useFavorites(){
const context = useContext(FavoriteContext)
if(!context){
throw new Error('useFavorites must be used inside FavoriteProvider')
}
return context
}
### Register Provider in main.jsx
## Example
// File path: src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import{ RouterProvider } from 'react-router-dom'
import{ FavoriteProvider } from './context/FavoriteContext'
import router from './router'
import'./index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
{/* Provider wraps the outermost layer, all components can access */}
)
> Why put Provider outside RouterProvider? Because the navigation bar NavBar (in App) also needs to read the favorites state. Provider must wrap all components that need to access this state.
* * *
## Using Favorites Feature in Components
### Favorite Button in BlogCard
## Example
// File path: src/components/BlogCard.jsx
import{ Link } from 'react-router-dom'
import{ useFavorites } from '../context/FavoriteContext'
function BlogCard({ id, title, summary, date, category }){
const{ isFavorite, toggleFavorite }= useFavorites()
function handleFavorite(e){
e.preventDefault()// Prevent Link navigation
toggleFavorite(id)
}
YouTip