How I’m Still Managing State in React with Just Context (and Why It Still Works in 2025)
If you've been working with React for a while, you know the state management landscape can get overwhelming fast. Zustand, Jotai, Recoil, Redux Toolkit, atoms, proxies, signals—you name it. But here’s the thing: I’m still using React’s built-in Context API with useReducer—and it’s working just fine. I want to show you how I structure it, why it still holds up in 2025, and what I’ve learned by keeping things simple. Why I Haven’t Switched to Zustand, Redux, or Jotai I’ve played around with a lot of state management libraries over the years. They’re impressive in their own ways. Zustand is sleek. Jotai is elegant. Redux (especially with Toolkit) has matured a lot. But at the end of the day, my projects just haven’t needed the extra overhead. Context and useReducer give me: Full control and transparency No new dependencies or mental models Easy debugging and testing Just enough flexibility for most app-scale needs For example, here’s exactly how I manage my shopping bag state in a React app: import { createContext, Dispatch, FC, ReactNode, useContext, useEffect, useMemo, useReducer, } from "react"; interface BagItem { id: string; name: string; quantity: number; price: number; } interface BagState { bag: BagItem[]; checkoutIsOpen: boolean; } interface BagContext { state: BagState; setState: Dispatch; } const initialState: BagState = { bag: [], checkoutIsOpen: false, }; export const BagContext = createContext({}); export const useBag: () => Partial = () => useContext(BagContext); interface BagProviderProps { children: ReactNode; } const BagProvider: FC = ({ children }) => { const [state, setState] = useReducer( (oldState: BagState, newState: Partial) => ({ ...oldState, ...newState, }), initialState ); useEffect(() => { const localBag = localStorage.getItem("bag"); if (localBag) { const payload: BagItem[] = JSON.parse(localBag); setState({ bag: payload }); } }, []); const api = useMemo( (): BagContext => ({ state, setState, }), [state] ); return ( {children} ); }; export default BagProvider; This setup gives me: A globally accessible bag state A clean useBag() hook Persisted state via localStorage No external libraries So Why Not Reach for Something Else? If I ever find myself dealing with complex derived state, undo/redo functionality, or shared state across iframes/tabs—then sure, I might reach for Zustand or Redux. But most apps don’t need that. React’s built-in tools have improved, especially with features like: useOptimistic (React 19) Server Actions (Next.js) React Server Components And as React continues to shift work to the server, I think we’ll rely less on big client-side state tools—not more. Final Thoughts You don’t always need to chase the next hot library. If Context + hooks do the job—and they often do—stick with them. This setup has worked for me across a bunch of apps, and until I feel real pain, I’m happy keeping things simple and transparent. What about you? Are you managing state the old-fashioned way, or have you gone all-in on Jotai, Zustand, or Signals? Let’s chat—drop a comment with your current stack

If you've been working with React for a while, you know the state management landscape can get overwhelming fast. Zustand, Jotai, Recoil, Redux Toolkit, atoms, proxies, signals—you name it.
But here’s the thing:
I’m still using React’s built-in Context API with useReducer
—and it’s working just fine.
I want to show you how I structure it, why it still holds up in 2025, and what I’ve learned by keeping things simple.
Why I Haven’t Switched to Zustand, Redux, or Jotai
I’ve played around with a lot of state management libraries over the years. They’re impressive in their own ways. Zustand is sleek. Jotai is elegant. Redux (especially with Toolkit) has matured a lot.
But at the end of the day, my projects just haven’t needed the extra overhead.
Context and useReducer
give me:
- Full control and transparency
- No new dependencies or mental models
- Easy debugging and testing
- Just enough flexibility for most app-scale needs
For example, here’s exactly how I manage my shopping bag state
in a React app:
import {
createContext,
Dispatch,
FC,
ReactNode,
useContext,
useEffect,
useMemo,
useReducer,
} from "react";
interface BagItem {
id: string;
name: string;
quantity: number;
price: number;
}
interface BagState {
bag: BagItem[];
checkoutIsOpen: boolean;
}
interface BagContext {
state: BagState;
setState: Dispatch<Partial<BagState>>;
}
const initialState: BagState = {
bag: [],
checkoutIsOpen: false,
};
export const BagContext = createContext<Partial<BagContext>>({});
export const useBag: () => Partial<BagContext> = () => useContext(BagContext);
interface BagProviderProps {
children: ReactNode;
}
const BagProvider: FC<BagProviderProps> = ({ children }) => {
const [state, setState] = useReducer(
(oldState: BagState, newState: Partial<BagState>) => ({
...oldState,
...newState,
}),
initialState
);
useEffect(() => {
const localBag = localStorage.getItem("bag");
if (localBag) {
const payload: BagItem[] = JSON.parse(localBag);
setState({ bag: payload });
}
}, []);
const api = useMemo(
(): BagContext => ({
state,
setState,
}),
[state]
);
return (
<BagContext.Provider value={api}>
{children}
</BagContext.Provider>
);
};
export default BagProvider;
This setup gives me:
- A globally accessible bag state
- A clean useBag() hook
- Persisted state via localStorage
- No external libraries
So Why Not Reach for Something Else?
If I ever find myself dealing with complex derived state, undo/redo functionality, or shared state across iframes/tabs—then sure, I might reach for Zustand or Redux.
But most apps don’t need that.
React’s built-in tools have improved, especially with features like:
- useOptimistic (React 19)
- Server Actions (Next.js)
- React Server Components
And as React continues to shift work to the server, I think we’ll rely less on big client-side state tools—not more.
Final Thoughts
You don’t always need to chase the next hot library.
If Context + hooks do the job—and they often do—stick with them.
This setup has worked for me across a bunch of apps, and until I feel real pain, I’m happy keeping things simple and transparent.
What about you? Are you managing state the old-fashioned way, or have you gone all-in on Jotai, Zustand, or Signals?
Let’s chat—drop a comment with your current stack