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 callingsetState()
afterwards may replace the mutation you made. Treatthis.state
as if it were immutable.
setState()
does not immediately mutatethis.state
but creates a pending state transition. Accessingthis.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 inshouldComponentUpdate()
. If mutable objects are being used and the logic cannot be implemented inshouldComponentUpdate()
, callingsetState()
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
ord
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
How to Use a Variable in a Regular Expression
Persist Variables Between Page Loads
JavaScript by Reference Vs. by Value
How to Sort an Array of Objects by Multiple Fields
Benefits of Using 'Object.Create' For Inheritance
Difference Between a Function Expression VS Declaration in JavaScript
What Do Multiple Arrow Functions Mean in JavaScript
JavaScript Raises Syntaxerror With Data Rendered in Jinja Template
How to Get the Browser Viewport Dimensions
Working With $Scope.$Emit and $Scope.$On
What Is the !! (Not Not) Operator in JavaScript
How to Remove a Property from a JavaScript Object
Send Post Data Using Xmlhttprequest
Why Can't I Directly Modify a Component'S State, Really
Using the Variable "Name" Doesn't Work With a Js Object
Uploading Both Data and Files in One Form Using Ajax
Do You Recommend Using Semicolons After Every Statement in JavaScript