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

May 11, 2025 - 11:42
 0
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<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