"Context + useState" vs "Context + useReducer"

What is State Management, and Why Does It even Matter?

State is the engine of any React app. It decides how components interact, update, and re-render. Poorly managed state often leads to bugs, unnecessary re-renders, and complex debugging. Choosing the right state management pattern ensures your application is scalable, predictable, and easy to maintain.

React’s context API

React’s Context API provides a way to share global state without manually passing props through multiple layers of components. This is especially useful for user authentication, theme switching (light/dark mode thingy), language preferences, shopping carts features.

Instead of prop drilling, Context makes state accessible throughout the component tree in a clean way.

In fact, when I was working at GridFlow on an EV vendor solution management app, we faced these exact decisions about how to handle state. From NFC tariff logic to team access control, we had to decide when useState was enough and when useReducer made more sense. Later in this article, I’ll share how our team navigated those choices and kept the architecture simple enough for interns while still building a scalable system.

Understanding useState in React

Let’s quickly go over what useState and useReducer are used for (if you’re not already familiar).

useState Hook

The useState hook lets you declare local state variables in functional components. It’s simple and ideal for managing small pieces of state.

const [count, setCount] = useState(0);

Benefits of Using useState with Context

  • Easy to understand and implement

  • Best for small-scale apps

  • Minimal boilerplate code

  • Quick setup with fewer moving parts

Limitations of useState in Larger Applications

  • Can become unwieldy with complex state logic

  • Multiple useState hooks may clutter code

  • Harder to maintain as state relationships grow

useReducer in React

The useReducer hook is an alternative to useState when dealing with complex state logic. It works similarly to Redux by using a reducer function that takes in the current state and an action, then returns a new state.

const [state, dispatch] = useReducer(reducer, initialState);

Benefits of Using useReducer with Context includes

  • Better for complex state transitions

  • Centralized logic inside the reducer function

  • Easier debugging and testing

  • More predictable state changes

Limitations of useReducer in Certain Scenarios

  • Boilerplate-heavy compared to useState

  • Overkill for simple state management

  • Beginners may find it harder to grasp

Context + useState

If your app has basic global state (like theme toggling or storing a user ID), useState with Context is often enough.

const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

Context + useReducer

If your app has multiple related states or complex transitions, useReducer shines. Think of shopping carts, form handling, or multi-step workflows.

const CartContext = createContext();

function cartReducer(state, action) {
  switch (action.type) {
    case "ADD_ITEM":
      return { ...state, items: [...state.items, action.payload] };
    case "REMOVE_ITEM":
      return { ...state, items: state.items.filter(i => i.id !== action.payload) };
    default:
      return state;
  }
}

function CartProvider({ children }) {
  const [state, dispatch] = useReducer(cartReducer, { items: [] });
  return (
    <CartContext.Provider value={{ state, dispatch }}>
      {children}
    </CartContext.Provider>
  );
}

Differences Between Context + useState vs Context + useReducer

  • useState: Simple, direct, great for beginners.

  • useReducer: Structured, but more verbose.

  • For small apps → useState + Context.

  • For larger apps with complex state → useReducer + Context.

Both work well with React Context, but useReducer can reduce unnecessary re-renders by grouping state changes.

Which approach should you use for your project? - Depends…

  • App size: Small → useState, Large → useReducer

  • State complexity: Simple → useState, Complex → useReducer

  • Team experience: Beginners → useState, Experienced devs → useReducer

Best Practices

  • Please don’t over-engineer with useReducer if you don’t need it.

  • Use Context sparingly to avoid unnecessary re-renders.

  • Consider external libraries like Zustand or Redux (my favorite) for very large apps.

“The GridFlow incident”: Building an EV Vendor Solution at GridFlow

When I was working at GridFlow, we started building an EV vendor solution management app from scratch. The app needed to handle vendor charger management. customer profiles, NFC tariff management, team management & access control**,** and other features…

The Challenge

We faced a big question:
How do we manage state so that it’s easy for interns to understand, yet scalable enough for advanced features like NFC-based tariff logic and role-based access control?

If we only used useState, the code risked becoming fragmented with too many local hooks. On the other hand, jumping into Redux or another heavy library would overcomplicate onboarding for interns.

What did we do?

We adopted a hybrid approach:

  • Context + useState for simple UI states (like toggles, filters, vendor dashboard views).

  • Context + useReducer for complex workflows (like managing tariffs, updating roles, and handling access control).

Personally, I think this struck the perfect balance, Interns could contribute and use useState whenever they needed to.

The app actually grew smoothly

Completing the tariff management feature became predictable through clear reducer actions. access control feature stayed centralized. the overall architecture remained clean, testable, and scalable.

By choosing wisely between useState and useReducer, we ensured the project stayed developer-friendly while still meeting requirements.

My conclusion is…

When deciding between Context + useState vs Context + useReducer, think about the complexity of your state and the size of your app. For small, simple state management, stick with useState. For larger, more complex state handling, useReducer provides better structure and maintainability.

“The GridFlow incident” shows how blending both approaches in the same project can deliver a balanced, developer-friendly, and scalable solution.

👉 For a deeper dive into React state management, check out the official React Docs.

1
Subscribe to my newsletter

Read articles from Shina-kelani Taiwo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shina-kelani Taiwo
Shina-kelani Taiwo

hey There I'm Taiwo Shina-Kelani. I'm a frontend developer and a technical writer.. I love meeting new people and exploring new technologies. I am also intrigued by Rust and love Rust language rustacian . I love blogging and tweeting my opinions and skills.