DEV Community

Cover image for A cure for React useState hell?

A cure for React useState hell?

Steve Sewell on January 12, 2023

Do you ever find yourself in React useState hook hell? Yeah, this good stuff: import { useState } from "react"; function EditCalendarEvent() {...
Collapse
 
manot40 profile image
manot40

This post literally changed my life

Collapse
 
dikamilo profile image
dikamilo

It's bad designed anyway. This EditCalendarEvent component, have a lot of responsibility. Having 6 input fields, you will re-render everything every time you change any of that fields because you share state and useEffect or useReducer doesn't matter here.

Collapse
 
brense profile image
Rense Bakker

Rerender is ignored for the other 5 fields if their values didnt change, the props will be the same and it will keep the DOM elements that are already there for those fields.

How would you suggest to make a component to edit some data model, like a calendar event, that has multiple fields (e.g. name, date, description, etc.)? It's a pretty common case to have modals like these with multiple input fields and the solution suggested by the author is pretty good imho.

Collapse
 
dikamilo profile image
dikamilo • Edited

EditCalendarEvent components holds global state as single states with useState or as single object with useReducer, so it will re render whole component on each change. With useReducer new object is returned so it have same component update behaviour as using multiple useState. Only one change is here that you have validation logic in one place.

You can create separate components for inputs that will hold own state or use refs instead of state for that data.

A lot of form don't require that anyway, you can just get form data in onSubmit callback like so:

onSubmit={(e) => submitForm(new FormData(e.currentTarget))
Enter fullscreen mode Exit fullscreen mode

useReducer is nice for things that changes together, form independent input fields in form, not so much.

Thread Thread
 
brense profile image
Rense Bakker

I dont think you can consider the event data as global state, its bound to the specific form component. You have to consider the case here that you are fetching the event data from a database (when editing for example). You are not going to do individual database queries for each field, so you need some way to pass down the event data to the individual input components.

There is no issue with this aslong as you keep the values referentially equal, so React is able to determine that the props/state for the other inputs didnt actually change. If React can determine that the props/state did not change, it will not make any DOM changes for those components.

The example by the author with the useReducer hook, is a good/clean way to achieve this.

Thread Thread
 
dikamilo profile image
dikamilo

I dont think you can consider the event data as global state, its bound to the specific form component.

Yes, I mean "global data" in context of this specific components tree.

Collapse
 
wiseai profile image
Mahmoud Harmouch • Edited

Great share! However, you can also achieve the same behavior with useState using a curried function ( also with the ability to supply a function that controls state transitions ):

import { useState } from "react";

function EditCalendarEvent() {
  const [event, setEvent] = useState({
    title: "",
    description: "",
    attendees: [],
  });

  const handleChange = (prop) => (e) => {
    // Validate and transform event to ensure state is always valid
    // in a centralized way
    // ...
    setEvent({ ...event, [prop]: e.target.value });
  };

  return (
    <>
      <input
        value={event.title}
        onChange={(e) => handleChange("title")(e)}
      />
      {/* ... */}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kamtoeddy profile image
Kamto Eddy

I've done this before but slightly different

const handleChange = (prop) => (e) => {
    // Validate and transform event to ensure state is always valid
    // in a centralized way
    // ...
    setEvent(event => ({ ...event, [prop]: e.target.value }));
  };
Enter fullscreen mode Exit fullscreen mode

Just to be sure I get the most up-to-date state

Collapse
 
shareef profile image
Mohammed Nadeem Shareef

I also do in this way. Just adding a point, instead of

setEvent(event => ({ ...event, [prop]: e.target.value }));

do

setEvent(prevEvent => ({ ...prevEvent, [prop]: e.target.value }));

It's not much of a difference but you would have a better code readability.

Collapse
 
fyodorio profile image
Fyodor • Edited

— A cure for React useState hell?
— Angular!
😅

If seriously, great post, thank you 👍

Collapse
 
brunoenribeiro profile image
Bruno Ribeiro

How so? Serious question here. I've been away from Angular for some time, I'd be really pleased to know how Angular deals with this nowadays.

Collapse
 
tami1901 profile image
Tamara Lužija

Until now, I have always avoided useReducer, but after your explanation, I won't anymore. Thank you:)

Collapse
 
mariamarsh profile image
Maria 🍦 Marshmallow

I believe some excellent points have been made in this post. I particularly like the section where you discuss your thoughts on cases where using useState or useReducer, leaves a lot to be considered!

Collapse
 
gabizz profile image
Gabriel Claudiu Maftei • Edited

Great and clear explanation!
You are so true, each and every piece of example found online implies the switch part, making it somehow more difficult to understand the whole pattern.

This, combined with React context is for some time now, my everyday state-management solution. Simple and clear!

Thank you for clearing things up a little for everyone!
Greetings from Romania!

PS: this article should be in the official react.js documentation :)

Collapse
 
michal1024 profile image
michal1024

I never thought of using dispatch function to carry updated state values instead of actions - this is really cool!

Collapse
 
nikitababko profile image
Nikita Babko

Great post, thank you 👍

Collapse
 
sanjeevkse profile image
sanjeev shetty

This is what happens, when we are not limited in our mind for thinking, experimenting and exploring possiblilities to simplify things.
Awesome post.

Collapse
 
nikolasbarwicki profile image
Nikolas ⚡️

Great in-depth review of using useReducer. It's often avoided by the developers but after understaning how to use it - it solves many issues!

Collapse
 
dennysjmarquez profile image
Dennys José Márquez Reyes

🤜🤛🤓

Collapse
 
renaldodev profile image
Renaldo Mateus

Mobx + dependency injection handles those perfectly.

Collapse
 
mmcshinsky profile image
Michael McShinsky

Great article on looks at the pros and cons. useState can access the previous state as well. When used as prev => ...prev over ...state, you get the same benefits. A large section of this article's benefits of useReducer just for the previous state can be negated as a result of the same functionality in useState.

Collapse
 
joaom123 profile image
João Rocha

I think the trade-off isn't that good. First you add a lot of code complexity the cognitive load needed to read your code is too great. Second if you have a "useState hell" that usually means your component is too big and you should break it into smaller forms/components.

Collapse
 
apperside profile image
Apperside

It also depends a lot on the use case.
In the case of a form for example, I would prefer delegating all the validation and state management to react-hook-form + yup, much more cleaner, you don't need any state at all..

Collapse
 
elsyng profile image
Ellis

Imho, if you find your component has a big state and you're looking for solutions how to manage this big state:
-- you should instead divide your component into smaller components.

I believe that's the essence of React and of component based programming.
:)

Arguably in most cases a component should have just a single line of useState.

Collapse
 
itminhnhut profile image
itminhnhut

in other words components & api context
in my opinion use zustand.

Collapse
 
badpractice profile image
Bad Practice

Call me the enemy of the state, but I will always use classes over anything.

Collapse
 
ave profile image
Alexander • Edited

"enemy of the state" <- pun intended :D ?

That is because you know the power of classes ;). In the world of JS it is rare enough thing.
Everyone was so into "hating classes" until hooks came out and everyone was soo into it :) and some time later we have an ocean of articles with the titles that go like "useState hell", "useEffect is so dangerous" and so on and so forth.

If I recall correctly there was only one issue with the classes: they were uhmmm, "awkward" to many JS developers. Oh and that weird "this" behaviour that would bite everyone at least once ;).

Well, hooks look like a fair trade of :)

Collapse
 
dennysjmarquez profile image
Dennys José Márquez Reyes • Edited

🤜🤛 🤓

Collapse
 
nicekiwi profile image
Ezra Sharp

Junior dev me would love this, experienced dev me 🫣 nope nope nope. Obscuring code for the sake of brevity, bad bad bad.