Why Can't I Directly Modify a Component'S State, Really

Why can't I directly modify a component's state, really?

The React docs for setState have this to say:

NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.

Basically, if you modify this.state directly, you create a situation where those modifications might get overwritten.

Related to your extended questions 1) and 2), setState() is not immediate. It queues a state transition based on what it thinks is going on which may not include the direct changes to this.state. Since it's queued rather than applied immediately, it's entirely possible that something is modified in between such that your direct changes get overwritten.

If nothing else, you might be better off just considering that not directly modifying this.state can be seen as good practice. You may know personally that your code interacts with React in such a way that these over-writes or other issues can't happen but you're creating a situation where other developers or future updates can suddenly find themselves with weird or subtle issues.

Is it safe to modify a react state directly, if you don't need a rerender?

I don't think the c and d you're describing (members that shouldn't cause re-rendering, and which are not used for rendering) are state information as the term is used in React. They're instance information. The normal way to use non-state instance information in function components is to use a ref.

On a pragmatic bits-and-bytes level, can you hold non-state information in state and modify it directly instead of using a state setter (directly or indirectly)? Yes, you probably can, at least initially. The only scenarios where I can see that causing incorrect behavior of the app/page involve rendering, and you've said they aren't used for that.

But if you do:

  • It'll be confusing for other team members (or you yourself, if you have to come back to the code after a break). Semantics matter. If you call it state, but it's not state, that's going to trip someone up. It's like calling something a function that isn't a function: at some point, someone's going to try to call that "function."
  • It'll be a maintenance hazard, because a team member (or you yourself after a break) may make an innocuous change such that c or d are used for rendering (because after all, they're in state, so it's fine to use them for rendering), perhaps by passing one of them as a prop to a child component. Then you're in the situation where the app won't rerender properly when they change.

A slight tangent, but in a comment on the question you mentioned that you "...they are all related and I often need to pass them all together to a function..."

Using a ref to hold c and d, the set up might look like this:

const [{a, b}, dispatch] = useReducer(/*...*/);
const instanceRef = useRef(null);
const {c, d} = instanceRef.current = instanceRef.current ?? {c: /*...*/, d: /*...*/};

Then getting an object in order to treat them as a unit is:

const stuff = {a, b, c, d};
// ...use `stuff` where needed...

Creating objects is very in expensive in modern JavaScript engines, since it's a common operation they aggressively optimize. :-)

Why changing state object property isn't needed any setState in React?

You can change the value of Array or Object, but you can not access the modified variable immediately inside your component it can not re-render your components because this is happening just when the states change, and it makes bug in your code.
The correct way is setting state and track the changed state in your component.

React: mutate state directly and force component to render

If you know what and why are you doing, you can do it. In your artificial example, the official React way is just a way cleaner, and efficient, thus that's the way to go

const Counter = () => {

const [clicks, setClicks] = useState(0);

return (
<div>
<p>
Hi, my name is {name}.
You have clicked {clicks} times.
</p>
<p>
<button style={{
backgroundColor: 'gray',
border: '1px solid black'
}} onClick={() => setClicks(1 + clicks)}>Click Me!
</button>
</p>
</div>
)
}

Though, in a scenario you described, where copying something for the sake of updating is prohibitive inefficient, or bad for other reasons, you actually can do something along the lines of you example. Though, no need to use state then, as React has useRef() hook for such stuff, which should be persistent to the component instance, but should not directly mess with render-triggering logic. So, if you want to go that way, I would do:

const Counter = () => {

const { current: heap } = useRef({ clicks: 0 });
const [epoch, setEpoch] = useState(0);

const increment = () => {
heap.clicks += 1;
setEpoch(1 + epoch);
}

return (
<div>
<p>
Hi, my name is {name}.
You have clicked {heap.clicks} times.
</p>
<p>
<button style={{
backgroundColor: 'gray',
border: '1px solid black'
}} onClick={increment}>Click Me!
</button>
</p>
</div>
)
}

Why we can't change states in React without calling setState()

The reason you need to use the setState() method is because of a concept called mutability.

When state changes in a react component, you generally want this to trigger a re-render of the component (to reflect these changes). Using setState will always trigger a re-render unless shouldComponentUpdate prevents this. By setting the property on the state object like so this.state.vote = this.state.vote + 1, you are altering the existing state object. Due to the way objects in javascript work, React can't easily tell that this object has changed.

When you use React's setState() method, you are creating a new version of the state object which React recognises and in turn knows it needs to re-render the component.

That's a simplified explanation but hopefully it explains the core concept to you.

Here's an interesting article to read about mutation

React: updating one item of the list (setState) instead of all

One way you can fix this is by using a implementing shouldcomponentupdate
but that method should be implemented when you need some performance improvement not just to stop re renderings

This method only exists as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs.

Next thing you can consider is using PureComponent

to implemented in that way you need to change the way you handle state update

handleMyChange = (index) => {

// Don't mutate the state in react it could lead in to unexpected results
// let newState = [...this.state.list];
// newState[index].value = Math.round(Math.random() + 10);

const newState = this.state.list.map((item, idx) => {
if (idx === index) {
// Replacing with a new item, so the other component can understand it's a new item and re-render it.
return {
...item,
value: Math.round(100 + Math.random() * 5)
};
}

// Return the same item, so the other component do not render since it's the same item.
return item;
});

this.setState(() => ({
list: newState
}));
};

class ListItem extends React.PureComponent {
......

React.PureComponent implements it with a shallow prop and state comparison, inorder to detect it as a new update we have to tell the ListItem component the props are different, to do that we should avoid mutating object in the array, instead we have to create a new item and replace the old array element.

Here is an good example of mutating state in react

Here is the updated sandbox

Why I can't get update the state of my react component?

It's best practice to never mutate React state, which you are doing with node.children.push as node is a reference to the state data.

I would recommend that addChild should return a new object altogether that setData would be called with. Or I don't see why addChild couldn't call setData itself. But either way data should be cloned, and the clone is what should be mutated and passed to setData.

Also, with the functional nature of React, it is also best practice to not have functions mutate their parameters as addChild does.

React component not updating after prop is updated using setState

State updates in reactjs should be immutable, you can think of this as meaning that you shouldn't update/mutate your current state in any way, but rather, create a new state value and apply your changes to that. You're currently mutating your state and then setting your state back to the mutated state value:

action==="upvote" ? comments[i].score++ : comments[i]--
setComment(comments)

From React's perspective, the new comments array that you're setting as your state is the same as your old comments state (ie: if you were to compare the old state and the new state with ===, you would get back true), so React doesn't rerender as it can't see the state change. Instead, you should be creating a new comments array (below this is created with .map()), and then creating new inner comment objects when you want to update the score value:

const upvotePost=(id, action)=> {
const mult = action === "upvote" ? 1 : -1;
setComment(
comments => comments.map(comment => comment.id === id
? {...comment, score: comment.score + (1*mult)}
: comment
)
);
}

Notice that above we never update the comments state directly, but instead return a new array, with a new object for the ones we want to update. The new object is created by using {...comment, score: comment.score + (1*mult)}, where the ...comment adds all of the (own enumerable) keys from the current object (see the spread syntax ...), and then sets the score key on the newly created object to an updated value. The arrow function in the setComments() is being used to obtain the most up to date version of comments before we update it - see functional updates.

Another issue is that you don't need your score state in your Comment component. The useState(upvotes) sets the score value to the value of upvotes on the intial render of your component, but when the upvotes prop changes, your score state won't change with it. As you're not using setScore, you can remove this state and instead use upvotes directly.



Related Topics



Leave a reply



Submit