State Management in React: useState vs useReducer vs Redux

Chris TiquisChris Tiquis
3 min read

Managing state is a fundamental part of building React applications. As your app grows in complexity, choosing the right state management approach becomes crucial for maintainability and performance.

In this post, we'll break down three popular state management techniques in React—useState, useReducer, and Redux—and explore when to use each one.


🔹 1. useState: Simple and Effective for Local State

useState is the most basic and commonly used hook for managing state in functional components.

When to Use:

  • You need to manage simple UI or component-level state.

  • State transitions are straightforward.

Example:

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Pros:

  • Simple and intuitive.

  • Great for isolated local state.

Cons:

  • Becomes unwieldy when managing multiple related states or complex logic.

🔸 2. useReducer: For Complex Local State Logic

useReducer is ideal when state logic is complex, involves multiple sub-values, or depends on the previous state.

When to Use:

  • Multiple related pieces of state.

  • Conditional or complex update logic.

  • You want a Redux-like pattern without external libraries.

Example:

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}

Pros:

  • Keeps logic centralized and predictable.

  • Easier to test and debug.

Cons:

  • Slightly more boilerplate than useState.

🔺 3. Redux: Global State Management at Scale

Redux is a standalone library designed for managing global state across an entire application. It’s often used when different components need to share and update the same state.

When to Use:

  • You have a large application with deeply nested components.

  • You need predictable state flow with middleware (e.g., logging, async).

  • Global state needs to be accessible and modifiable from many places.

Example (Redux Toolkit):

// counterSlice.ts
const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: state => state + 1,
    decrement: state => state - 1,
  },
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
// Counter.tsx
const count = useSelector((state) => state.counter);
const dispatch = useDispatch();
<button onClick={() => dispatch(increment())}>+</button>

Pros:

  • Centralized, consistent state logic.

  • DevTools support, middleware, and ecosystem.

  • Works well with large teams and complex apps.

Cons:

  • Adds complexity and boilerplate.

  • Overkill for small projects.


✅ Choosing the Right Tool

CriteriauseStateuseReducerRedux
Simplicity⭐⭐⭐⭐⭐⭐⭐⭐
Local state🚫
Complex logic🚫
Global state🚫🚫
Boilerplate🚫⚠️⚠️⚠️
Best forSimple UI stateRelated state w/ logicLarge-scale apps

🔚 Conclusion

There’s no one-size-fits-all solution to state management in React. Use useState for simple cases, useReducer for complex local logic, and Redux (preferably with Redux Toolkit) when you need full-scale global state management.

Start simple, and only introduce complexity when the application demands it.

Happy coding! 🧠⚛️


0
Subscribe to my newsletter

Read articles from Chris Tiquis directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Chris Tiquis
Chris Tiquis