Why is this counted as mutating state? - javascript

handleClick(event) {
let value = event.target.value;
this.setState({ question: (this.state.question += value) });
I get a warning:
Do not mutate state directly. Use setState()
react/no-direct-mutation-state
if I try to load the page with this code.
How can I fix it so it doesn't give me this warning?
It says to use this.setState, but that's what I'm doing.

You're doing an unnecessary assignment addition to this.state.question - you only need addition here. Furthermore, when updating state based on a previous value, the docs state:
React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
The proper way to update state based on a previous value is to pass in an update function that works off the previous value, i.e.:
this.setState(prevState => ({ question: prevState.question + value }));

The mistake is here :
this.state.question += value
+= is the operator used to add right operand to the left operand. You are trying to add value to this.state.question.
The correct way to do it is :
this.setState(previousState => ({ question: previousState.question + value }));

This is considered mutating state because you are assigning a value to this.state.question in the expression (this.state.question += value). I have pasted a link to a Code Sandbox you can view to see how to correctly implement this behavior.
Code Sandbox

Related

Issue using Ternary operator in UseState

I'm trying to do the following
const[name,setName]=useState(recipe!==null?recipe.name:'')
The name appears to never be assigned with the ternary operator. Do they just not work within useState?
I've also tried reassigning the name like:
if(recipe!==null){
setName(recipe.name)
I receive an error in the console about too many re-renders.
Is there any other way of assigning name conditionally?
Edit
This is the structure of recipe
const[recipes, setRecipes]=useState([
{
id:1,
name:'Chicken Curry',
ingredients:"Some ingredients",
steps:"Some steps",
energy:'2899',
fat:'28.5',
carbohydrates:'41.3',
protein:'12',
sodium:'1226',
preparationTime:'15',
difficulty:'Easy'
}
How I'm calling the const
<RecipeModal recipe={activeRecipe}/>
and the recipe modal
const RecipeModal=({recipe}) =>{
It's difficult to understand what is going on there but I will try to help. I guess that the recipe can be undefined instead of null. You could try below:
const [name, setName] = useState(recipe ? recipe.name : '');
In example above, we are simply checking if the recipe has a truthy value so the default state value will be an empty string if recipe is: null, undefined, 0 or "".
Regarding reassigning name state. Are you doing that inside a useEffect hook? Please review all your useEffect hooks to see if you are not changing a value that is present in a dependency array.
Infinite re-renders can be solved by adding an if statement to see if the value we want to assign to did really change. It could be also related to a place where you reassign a recipe. I could help further with a bit more code.
Try to use like this:
const[name,setName]=useState(recipe!==null?recipe.name:null)
Or if name is string type, then
const[name,setName]=useState(recipe!==null?recipe.name: " ")

How does React determine if a reference type state is new?

I know that when a state is of primitive type, React performs a shallow comparison between the old state and the new when setState() is called, and will not rerender if the two are identical. When the state is reference type, however, React doesn't perform a deep comparison between new and old. Based on this test here, when I setState() with a newly constructed (though identical to a human eye) object, it rerenders the component. This is expected. However, when you return count (the original object returned by useState()) or prev with a functional setState(), React somehow knows that it is the same state and decides not to re-render. I am curious as to how it is able to determine that, despite count/prev being identical to { value:0 } when a deep comparison is performed?
Here's a rough version of the code inside the component (check the above snippet for demo):
console.log('Component re-rendered') // Will log if 'willRerender' is true
const [count,setCount] = useState({ value: 0 })
const handleUpdate = willRerender => {
console.log('setState is called')
if (willRerender) {
setCount(count) // The same effect is achieved with setCount(prev => prev)
} else {
setCount({ value: 0 })
}
}
Yes, because both of them have the same reference, count == count, but, count != {value: 0}, this is expected comparison behaviour of javascript.

React useState: pushing a sub-array into an array

Am using function based component and am having trouble in pushing a subarray into a useState array.
my code is shown below. There is an array called mode coming from the props which i need to append it as a sub array of more
const ViewCharts = (props) =>{
//other codes
let [more,setMore] = useState([])
useEffect(()=>{
console.log(props.mode,' mode array')
let temp = [...more,props.mode]
console.log(temp, ': this will append to more')
setMore(temp)
setTimeout(()=>{
console.log(more,'after setting')
},2000)
},[//some value])
}
when the props.mode has value ['top','bottom'] i expect more to have value of [['top','bottom']] and when next time if the props.mode is ['top'] i need more to have [['top','bottom'],['top']]. but this is what am getting when i run the above code.
["top"] mode array
["top"] : this will append to more"
[] : "after setting"
why the setMore is not adding array even when the temp is having the expected value.
If I remember correctly the useState variable will change value in the next render when you set it. You are trying to read the new more value in the same render you've changed it (as you are reading it in the same effect you've set the value in), which will be [] the first time as that's how you initialised it.
Try creating a second useEffect with a more dependency to see if it gives you the value you want:
// You can also just console.log() before the return, needing no `useEffect` to see if the value indeed changed.
React.useEffect(() => {
console.log('More: ', more);
}, [more]);
https://reactjs.org/docs/hooks-state.html#recap
Line 9: When the user clicks, we call setCount with a new value. React will then re-render the Example component, passing the new count value to it.
I would suggest reading the hooks API to better understand how they work.

`setNum(() => num++)` not working, but `setNum(() => num + 1)`

I am practicing React using Hooks and Context, working on a simple Quiz App. The score should increment to 1 when the answer is correct.
const { qa, questionNumber } = useContext(GlobalContext);
const [score, setScore] = useState(0);
const answerOnClick = (e) => {
const correct = qa[questionNumber].correct_answer === e ? true : false;
if (correct) {
setScore(() => score++);
}
};
But I'm getting this error on line setScore(() => score++);:
TypeError: Assignment to constant variable
I also tried if (correct) { score++; setScore(() => score); } and setScore(() => ++score), still not working.
But when I try setScore(() => score + 1);, now it increments!
I have learned that the Increment is a valid JS operator. Aren't score++ and score + 1 equivalent? And why score treat as a constant variable? It is mutable, right? I'm still a novice developer. Can someone explain what's happening here? Thank you.
The problem is, that score is defined as constant. This means that is should not be reassigned. When using score++ you're reassigning it as follows score = score + 1 but as a short hand. Your JavaScript interpreter does not like that you're reassigning a variable which you defined as being constant. Therefore you get the error.
The useState hook provides an update function (in your case setScore) which you should use to update the state. You're not directly changing the value of score. You're telling react to initialise score with a higher value on next render. Your components function is called again with a new declaration of score, this time with a higher value. As you correctly pointed out setScore(() => score + 1) works, however, setScore(score + 1) should work too.
You mutate the state when you do score++. You should not mutate the state in React because setNum is async.
https://reactjs.org/docs/react-component.html#setstate
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
score++/++score would mutate the state. The best way to update state based on previous state is this way:
setScore((prevScore) => prevScore + 1);

calculation and changing state inside setstate in reactjs

I have two states result which initially set to zero and val initially set to 1.
In the multiply function, I change the states as followed.
multiply(){
console.log('multiply is clicked');
console.log('numberformed is '+this.state.numberformed);
this.setState(()=>{
return{
val:this.state.val*this.state.numberformed,
result:this.state.val,
};
});
}
Here this.state.numberformed outputs 12 as I checked using console.
I want the result to be equal to 12 so I first changed val as it is seen in the code but the result still reads 1 which is the initial state of val and not the changed state of val.How should I do this?
The tricky part of setState is that the name is slightly misleading. It's actually more like scheduleStateUpdate.
The function you supply to setState returns an object that React will merge with this.state when it updates. Before the update happens, this.state always refers to the current state. Inside the return statement, the update definitely has not happened, because the update hasn't even been scheduled yet. The return statement is what schedules the update.
It looks like this might be what you were trying to do:
this.setState(() => {
const val = this.state.val * this.state.numberformed
return {
result: val
}
})
The brackets for the return statement might be tripping you up. Yes, it looks an awful lot like the brackets for a function body, or something, where statements go. But it's actually returning a javascript object from the function.
The statement
return {
result: val
}
Looks conceptually more like this:
return (
{ result: val }
)
This will add a key called result to this.state, accessible as this.state.result. In your code, assuming result was actually assigned the value you expected, two keys would have been added to state: val and result, both of which would have exactly the same value.
Since you're updating state with a simple calculation, you could make an even shorter version without using an intermediate variable like val or even a function at all:
this.setState({result: this.state.val * this.state.numberformed})
Or, more readably:
const { val, numberformed } = this.state
this.setState({result: val * numberformed})
This would set this.state.result to the result of the multiplication and leave every other key of this.state unchanged.

Categories