Pseudo-organization with useState vs using useReducer

Posted first on my linkedIn Recently, I’ve seen many discussions about the so-called “mess” caused by multiple useState calls in React components. The criticism often comes with a proposed "improvement": grouping all states into a single useState with an object, aiming to reduce lines of code and make the component appear more "organized." At first glance, this idea might seem elegant, after all, who doesn't want cleaner-looking code?But behind this pseudo-organization lie serious issues with performance, readability, and scalability. The trap of useState with an object: When you use a single useState with an object containing multiple state flags (like loading indicators), you're essentially linking all those flags to a single memory reference. In practice: Every time you update any key in the object, the entire object is recreated. Even if you use immutable updates (setState({ ...state, changedKey: value })), React sees this as a new full state because the reference has changed. This causes unnecessary re-renders, even when other properties haven’t changed. It affects performance directly, especially in components with heavy rendering or many states. There’s also a readability issue: You lose individual setters like setIsLoading(true), now you must manually manipulate a shared object. You lose the semantic clarity of each individual state, hiding them inside a structure that’s harder to reason about. The illusion of “cleaner code”: The main reason for adopting the object approach is to make the file look organized and reduce line count. But visual organization ≠ good architecture. Just because "they're all states" doesn't mean they belong together. This approach makes the state harder to: Debug Test Maintain What truly matters in code organization is making the logic explicit and resilient, not saving a few lines. The road with useReducer: When you start managing several related states, especially booleans like loading flags, form validations, request statuses, useReducer becomes a powerful alternative. It allows you to group related states logically while keeping updates isolated. A well-structured reducer makes state transitions explicit, predictable, and traceable. dispatch and type bring a Redux-like structure, but lighter and scoped locally. Independent updates won’t trigger unnecessary renders because state reactivity remains granular. **But, how to use it? Step 1: Define actions and initial state** We create the available actions and the initial state, which contains two boolean flags: isLoading and isSubmitting. Step 2: Create the reducer function The reducer function takes the current state and an action, then returns the updated state based on the action type. Step 3: Create the MiniLoader component using useReducer Inside the component, we use useReducer by passing the reducer and initialState. We define two functions that simulate asynchronous operations and update the corresponding loading states. To Infinity, And Beyond! Reducers can be unit tested in isolation. It’s easier to scale the state logic as your component grows. You can add new actions without disrupting existing flow. "In the crack of the eggs": Visual organization ≠ good architecture. Replacing multiple useStates with a single object feels like an optimization, but it’s a dangerous abstraction that harms maintainability, performance, and clarity. On the other hand, useReducer scales better, offers better control, and gives you a more predictable and reliable component structure. If your component is juggling multiple flags or tightly related states, stop fighting useStates, embrace the power of useReducer. It’s a small change that brings big benefits: cleaner, scalable, and more robust code. #ReactJS #useState #useReducer #ReactHooks #CleanCode #FrontendTips #WebPerformance #CodeQuality #ReactBestPractices #JavaScript #DevTips #FrontendArchitecture #ScalableCode #MaintainableCode

May 12, 2025 - 22:38
 0
Pseudo-organization with useState vs using useReducer

Posted first on my linkedIn
Recently, I’ve seen many discussions about the so-called “mess” caused by multiple useState calls in React components.
The criticism often comes with a proposed "improvement": grouping all states into a single useState with an object, aiming to reduce lines of code and make the component appear more "organized."

Image description

At first glance, this idea might seem elegant, after all, who doesn't want cleaner-looking code?But behind this pseudo-organization lie serious issues with performance, readability, and scalability.

The trap of useState with an object:
When you use a single useState with an object containing multiple state flags (like loading indicators), you're essentially linking all those flags to a single memory reference.

In practice:

  • Every time you update any key in the object, the entire object is recreated.
  • Even if you use immutable updates (setState({ ...state, changedKey: value })), React sees this as a new full state because the reference has changed.
  • This causes unnecessary re-renders, even when other properties haven’t changed.
  • It affects performance directly, especially in components with heavy rendering or many states.

There’s also a readability issue:

  • You lose individual setters like setIsLoading(true), now you must manually manipulate a shared object.
  • You lose the semantic clarity of each individual state, hiding them inside a structure that’s harder to reason about.

The illusion of “cleaner code”:
The main reason for adopting the object approach is to make the file look organized and reduce line count. But visual organization ≠ good architecture.

Just because "they're all states" doesn't mean they belong together.

This approach makes the state harder to:

  • Debug
  • Test
  • Maintain

What truly matters in code organization is making the logic explicit and resilient, not saving a few lines.

The road with useReducer:
When you start managing several related states, especially booleans like loading flags, form validations, request statuses, useReducer becomes a powerful alternative.

  • It allows you to group related states logically while keeping updates isolated.
  • A well-structured reducer makes state transitions explicit, predictable, and traceable.
  • dispatch and type bring a Redux-like structure, but lighter and scoped locally.
  • Independent updates won’t trigger unnecessary renders because state reactivity remains granular.

**But, how to use it?

Step 1: Define actions and initial state**

We create the available actions and the initial state, which contains two boolean flags: isLoading and isSubmitting.

Image description

Step 2: Create the reducer function
The reducer function takes the current state and an action, then returns the updated state based on the action type.

Image description

Step 3: Create the MiniLoader component using useReducer
Inside the component, we use useReducer by passing the reducer and initialState. We define two functions that simulate asynchronous operations and update the corresponding loading states.

Image description

To Infinity, And Beyond!

  • Reducers can be unit tested in isolation.
  • It’s easier to scale the state logic as your component grows.
  • You can add new actions without disrupting existing flow.

"In the crack of the eggs":
Visual organization ≠ good architecture. Replacing multiple useStates with a single object feels like an optimization, but it’s a dangerous abstraction that harms maintainability, performance, and clarity.

On the other hand, useReducer scales better, offers better control, and gives you a more predictable and reliable component structure.

If your component is juggling multiple flags or tightly related states, stop fighting useStates, embrace the power of useReducer.

It’s a small change that brings big benefits: cleaner, scalable, and more robust code.

#ReactJS #useState #useReducer #ReactHooks #CleanCode #FrontendTips #WebPerformance #CodeQuality #ReactBestPractices #JavaScript #DevTips #FrontendArchitecture #ScalableCode #MaintainableCode