Context works well for a handful of relatively simple, infrequently-changing global values. Once an app has complex, frequently-updating global state with many different update patterns โ a shopping cart, real-time collaborative data, complex UI state shared across many unrelated features โ dedicated state management libraries offer better performance and tooling (like time-travel debugging).
// A conceptual overview - Redux Toolkit (the modern standard) simplifies this setup significantly
import { configureStore, createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
decrement: (state) => { state.value -= 1; }
}
});
const store = configureStore({ reducer: { counter: counterSlice.reducer } });Redux centralizes all of an app's state into a single store, updated only through dispatched actions handled by reducers โ the exact same reducer pattern from Chapter 23, just scaled up to manage an entire application's state instead of one component's.
import { create } from "zustand";
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
function Counter() {
const { count, increment } = useCounterStore();
return <button onClick={increment}>{count}</button>;
}Zustand offers similar global-state capability with dramatically less boilerplate than traditional Redux โ no Provider wrapping required, and state is accessed directly via a custom hook. It's become a popular choice for teams wanting global state without Redux's full ceremony.
Server state vs client state is an important related distinction: data fetched from an API (server state) has different needs โ caching, refetching, staleness โ than purely local UI state (client state). Libraries like React Query/TanStack Query are purpose-built for server state specifically, often used alongside Zustand or Context for client state.
Redux (or any external state library) adds real setup overhead and a learning curve. For small to medium apps, useState, useReducer, and Context together are often completely sufficient. Introduce a dedicated state library when you actually feel the pain of prop drilling or scattered state logic across a large app โ not preemptively.
This chapter is conceptual โ as practice, write a short comparison (in comments) of when you'd choose Context alone versus Zustand versus Redux Toolkit for a given app's needs.
// Small app, a few shared values (theme, user) -> Context API
// Medium app, moderate global state, want minimal boilerplate -> Zustand
// Large app, many developers, need strict patterns + devtools -> Redux Toolkit
// Data mainly comes from a server and needs caching -> React Query / TanStack Query
Q Do I need Redux to learn React properly?
No โ Redux (or any external library) is an advanced, optional tool for specific scaling problems. Plenty of production React apps use only useState, useReducer, and Context successfully.
Q What's the difference between server state and client state?
Server state is data that actually lives on a backend and is fetched over the network (like a list of products) โ it can go stale and needs refetching. Client state is data that only ever exists in the browser (like whether a modal is open), which never needs syncing with a server.