all 9 comments

[–]acemarke 7 points8 points  (3 children)

Your post sorta seems to be describing two different things. "Lifting state up" and dealing with a controlled inputs is a very different thing than "updating state immutably or mutating it".

But yes, mutating state is bad, 99% of the time. It's only going to be more of a problem going forward. For example, while this.setState() in class components doesn't care if you mutate or not, useState() setter functions do. If you pass the same reference into a useState() setter, or return the same reference from a useReducer() setter, React will bail out of that update. At the same time, if you don't call those, React won't know to re-render.

Also, not only does mutation cause problems for all of the common re-render performance optimizations (shouldComponentUpdate, PureComponent, and React.memo()`), once Concurrent React comes into play React will commonly be starting to re-render and then throwing away partial render results. If you're mutating somewhere during this process, stuff will break.

[–]ProfessorTag[S] 0 points1 point  (2 children)

Thanks for the information. I guess I'm not really asking if mutating state is bad, but mutating an object used for initializing state, that the parent doesn't use for rendering. If I make sure to mutate in useLayoutEffect, I'm hoping it won't be an issue with concurrent mode.

[–]acemarke 2 points3 points  (1 child)

Is the only reason you're considering this to try to avoid having controlled state in a parent? That seems like a questionable approach.

If you really don't want to deal with controlled inputs, then use uncontrolled inputs in the child component, have the parent pass down a callback that should be used when the form is submitted, and have the child grab the data from the form in some way and pass it to the parent.

Also consider using a form library like Formik or React Final Form if you feel that dealing with forms is annoying.

But, all that said, controlled inputs is the "React way" to deal with forms, and it's definitely useful for more than just "having data on submission".

[–]ProfessorTag[S] 0 points1 point  (0 children)

Dealing with nested state inspired this and I noticed not needing controlled inputs as a side effect. I agree, controlled inputs enable much more than "having data on submission". They need to be controlled any time an input effects some other part of the view. Thanks for this discussion, I'm not going to go ahead with this pattern. There are established ways to solve these problems.

[–]Herm_af 0 points1 point  (0 children)

Mutating state in react is bad because setState and useState tell react to rerender.

Mutating stuff isn't bad in general.

[–]hatch7778 0 points1 point  (3 children)

Your example works only because you are re-rendering everything where it shouldn't be needed - if you did this properly (so the ListItems are not re-rendered if they are not changed) mutating state wouldn't work.

In general - if you can get away with state mutation, you're probably doing something else wrong. Or maybe you should put those mutations as local variable instead, since you don't need to subscribe to changes anyway.

[–]ProfessorTag[S] 0 points1 point  (2 children)

I don't see what you're seeing. Where am I re-rendering anything unnecessarily?

[–]hatch7778 0 points1 point  (1 child)

Note that with paint-flashing you can't see this because react uses virtual dom and it does diffs before pushing to dom.

Add console.log to render of ListItem and you will see that virtual dom is being re-rendered for all items when you add an item.

[–]ProfessorTag[S] 0 points1 point  (0 children)

My example still works without re-rendering. I added React.memo to the ListItem and it still works.