Mastering Global State in React: useReducer + Context Explained
This article explains how useReducer can replace useState for complex state logic, introduces Context for sharing data across components, demonstrates their combined usage with code examples, and compares this approach to React‑Redux, highlighting differences in data flow, rendering behavior, and suitable scenarios.
In React, useReducer can serve as an alternative to useState for managing state in complex logic scenarios, with a syntax similar to Redux. Context provides a way to share data between components without explicitly passing props through each level of the component tree.
useReducer Overview
useReducer can be seen as another form of useState; both have essentially the same functionality, but useReducer’s implementation mirrors Redux.
<code>const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div onClick={() => dispatch({ type: "increment" })}>{state.count}</div>
);
};
</code>Context Overview
When component nesting becomes deep, passing props layer by layer is cumbersome; Context solves this problem.
<code>const Context = React.createContext();
const Counter = () => {
const { count, add } = useContext(Context);
return <div onClick={() => add()}>{count}</div>;
};
const App = () => {
const [count, setCount] = useState(0);
const add = useCallback(() => setCount(count + 1), [count]);
const value = useMemo(() => ({
count,
add
}), [count, add]);
return (
<Context.Provider value={value}>
<Counter />
</Context.Provider>
);
};
</code>After using Context, Counter can access the App’s state data
count, which can be regarded as a global state. Context acts as a bridge, allowing inner components to access outer component state, while state management is handled by useState/useReducer. Below is a refactor using useReducer:
<code>const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
};
const ReducerContext = React.createContext();
const ReducerProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const store = useMemo(() => [state, dispatch], [state]);
return (
<ReducerContext.Provider value={store}>{children}</ReducerContext.Provider>
);
};
const useReducerContext = () => {
return useContext(ReducerContext);
};
const Counter = () => {
const [state, dispatch] = useReducerContext();
return (
<div onClick={() => dispatch({ type: "increment" })}>{state.count}</div>
);
};
const App = () => {
return (
<ReducerProvider>
<Counter />
</ReducerProvider>
);
};
</code>This Context + useReducer state management resembles React‑Redux in syntax, but there are notable differences:
Both rely on Context for value passing, but Context + useReducer passes the current state value, whereas React‑Redux passes the Redux store instance.
When the state changes in Context + useReducer, all subscribed components re‑render, even if they use only part of the state; React‑Redux allows components to subscribe to finer‑grained slices, re‑rendering only when those slices change.
Context + useReducer is suitable for moderately complex state management to avoid deep prop drilling, while React‑Redux fits more complex applications and supports middleware.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.