YouTip LogoYouTip

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(

Favorite count: {favoriteIds.length}

) } ### 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) }
← Django Blog Project InitReact Blog Search β†’