Sitemap

From useState to useReducer

4 min readFeb 2, 2026

--

React gives us multiple ways to manage state in functional components. Most beginners start with useState, which is simple and effective for handling small pieces of state. However, as applications grow, state logic can become harder to manage. This is where useReducer becomes extremely useful.

What is useReducer?

useReducer is a React Hook used for managing state through a reducer function and dispatched actions.

Instead of directly updating state, we dispatch an action, and the reducer function determines how the state should change. This approach is inspired by the same pattern used in Redux.

Why do we need useReducer when we already have useState?

useState works perfectly for simple and independent pieces of state. For example:

  • A counter value
  • A toggle (true/false)
  • A single input field

But as our component grows:

  • Many state variables
  • Many setState calls in a single function
  • State values depending on each other
  • Logic becoming scattered across the component

At this point, managing state with multiple useState calls becomes difficult to maintain and understand.

Get Jinetta Shree’s stories in your inbox

Join Medium for free to get updates from this writer.

useReducer solves this by:

  • Keeping all related state in one object
  • Keeping all state-changing logic in one reducer function
  • Making state transitions predictable and structured

Syntax of useReducer:

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer — function that takes current state and an action, and returns the new state
  • initialState — the default starting state when the component loads
  • state — the latest value stored and used by the component
  • dispatch — function you call to send an action to the reducer to update state

Reducer function syntax:

const reducer = (state, action) => {
switch (action.type) {
case "ACTION_NAME":
return newState;
default:
return state;
}
};
  • state — current state
  • action — object describing what happened
  • Reducer must always return new state
  • Reducer must be pure — no API calls should be there.

Qn: Why reducer should not contain any API calls?

Ans: API calls take time, do not return immediately. Having API call inside reducer breaks the state flow. Instant updates are handled by reducer whereas API takes time that's why API calls should not be used inside them.

Lets have a look at a small example for easy understanding.

import { useReducer } from "react";

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 };
case "RESET":
return { count: 0 };
default:
return state;
}
};

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

return (
<div>
<h2>Count: {state.count}</h2>

<button onClick={() => dispatch({ type: "INCREMENT" })}> + </button>
<button onClick={() => dispatch({ type: "DECREMENT" })}> - </button>
<button onClick={() => dispatch({ type: "RESET" })}> Reset </button>
</div>
);
};

export default Counter

DECODING THE ABOVE CODE

import { useReducer } from "react";

Importing useReducer hook from react.

const initialState = { count: 0 };

Created a constant variable named initialState , assigning it to a object that hold a key count to value 0 . This is the starting state of the component.

const reducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return { count: 0 };
default:
return state;
}
};

Again created a const variable named reducer , holds two parameters (state, action) , state contains the current state value, action describes the work that happens.

switch is a inbuilt JavaScript conditional state that is used when multiple conditions/functions are performed. Syntactically it contains case and optionally default .

In switch we are passing object action holding its property type used to identify what action type it is.

case its a part of switch. "" inside that string value is given any custom name based on the logic behind that.

return — reducer always return a new state, default — If the action type does not match any case, the reducer simply returns the current state without changes.

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

return (
<div>
<h2>Count: {state.count}</h2>

<button onClick={() => dispatch({ type: "INCREMENT" })}> + </button>
<button onClick={() => dispatch({ type: "DECREMENT" })}> - </button>
<button onClick={() => dispatch({ type: "RESET" })}> Reset </button>
</div>
);
};

export default Counter

Functional Component named Counter , useReducer is called with reducer and initialState, returns state and dispatch .

Qn: How dispatch works?

Ans: Think of dispatch as the messenger between your UI and the reducer. Here, When button clicked dispatch sends this action the reducer , reducer decides the new state declared there and returns a new state, react updates the state, component re-renders and now shows the updated value.

The function is then exported.

Remember, in the previous blog I added recent usecase example the same is tried using useReducer. Check them on my repo https://github.com/jinettashree/react-hooks/tree/main/src/useReducer/UserDetails

Once you get comfortable with this pattern, managing complex state becomes easier, more organized, and far less confusing.

--

--

Jinetta Shree
Jinetta Shree

Written by Jinetta Shree

Not an expert just being honest