ReactJS difference between setting state directly and through method argument - reactjs

I have started learning ReactJS and realize that there are two way I can change the state. Both works fine without any error or warning.
What is the main difference? is there a reason or need I should use second version?
consider I want to modify following state on each click
this.state = { count: 0 }
First Version
handleClick = () => {
this.setState({count: this.state.count+1})
}
Second Version
handleClick = () => {
this.setState(prvState => {
return {
count: prvState+1
}
})
}

If you are setting the next state value based on the previous state
value then passing in a function (second version) as the first
parameter of this.setState instead of an object is the recommended
solution.
handleClick = () => {
this.setState(prvState => {
return {
count: prvState+1
}
})
}
The reason is that this.state may be updated asynchronously and that's why you should not rely on their values for calculating the next state.
In the first version, there's a high possibility that the value of count is incorrect especially once your application gets bigger.

If you rely on the previous state (like in your case you need the previous counter value), the second approach is preferred. It guarantees that prvState holds the current state. This is important if you have two setState modifying the state in the same render loop (remember that React may batch multiple setState calls together), e.g.
this.setState({count: this.state.count+1})
this.setState({count: this.state.count+1})
has the problem that the count gets only incremented once. While
this.setState((prevState) => {count: prevState.count+1})
this.setState((prevState) => {count: prevState.count+1})
guarantees that it gets incremented twice as intended (independent of the order the setState are handled by React)

Related

React setInterval in combination with useEffect creates behavior I don't understand. It requires a callback function inside setState setter function

I am Following along in a course and I do not understand the logical reason for the different behavior in the two code examples. I start with the working example then I highlight what breaks the code. There are solutions for this problem in similar questions but I haven't found a description or term for what is happening.
Below the setInterval works correctly and the timer function counts down per second as it should. There is a secs useState hook to keep track of seconds, and an inProgress useState.
React.useEffect(() => {
if (!inProgress) {
clearInterval(interval.current)
} else {
interval.current = setInterval(() => {
setSecs((secs) => {
const timeLeft = secs - 1;
return timeLeft
})
}, 1000)
}
return () => clearInterval(interval.current);
}, [inProgress])
Below is the code excerpt that varies and breaks the code.
interval.current = setInterval(() => {
setSecs(secs - 1)
console.log('interval fires')
console.log(secs)
}, 1000)
This code does not work, it counts down for one second and then nothing happens. When using the console.logs as seen above, 'interval fires' prints every second as does 'secs', however the secs state is not counting down its stays the same number.
React.useEffect(() => {
if (!inProgress) {
clearInterval(interval.current)
} else {
interval.current = setInterval(() => {
setSecs(secs - 1)
}, 1000)
}
return () => clearInterval(interval.current);
}, [inProgress])
This seems like important behavior to commit to memory, yet I don't know how to categorize it. I'm looking for terms that describe this behavior or a good visual explanation of it.
The last setSec state ran 1000ms ago, and even If I setInterval to 10 seconds the problem persists. So this is not a render, mount, update issue based in time. I don't believe this can be described by closures either. I currently cannot logically understand this behavior.
This is because when you render your component, each render has its own props and states. So states and props are never be changed in the render.
In the useEffect with emtpy dependency array, you know that it will run on the first render of the component. It means that when you setInterval in this situation, you set it with the props and states which is dependent on the first render, and it will not be changed even if setInterval is fired.
In other words, your setInteval function will be the closure and the states will be the free variables. You are referencing this stale variable in the closure.
React.useEffect(() => {
if (!inProgress) {
clearInterval(interval.current)
} else {
interval.current = setInterval(() => {
setSecs(secs - 1) // state 'secs' are dependent on this render, free variable in this closure
}, 1000)
}
return () => clearInterval(interval.current);
}, [inProgress])
So if you want it to be reference recent state, you can use functional update form in setState. offical docs
setSecs(secs => secs - 1) // this form of setState guarantee that your state value is the recent one
But if you need to do something more than setState with state value, you should consider using useRef. Unlike states, ref will always give you the same object reference which is independent of your component. offical docs
You can learn more details in Dan Abramov's post which is really helpful to understand the logic behind the useEffect and states.

In React, why we need to render twice while we can do it render just once

In the article (https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html) from react docs, there is the following:
Refs can be useful in certain cases like this one, but generally we recommend you use them sparingly. Even in the demo, this imperative method is nonideal because two renders will occur instead of one..
here is a relevant part of the demo from https://codesandbox.io/s/l70krvpykl?file=/index.js:
handleChange = index => {
this.setState({ selectedIndex: index }, () => {
const selectedAccount = this.props.accounts[index];
this.inputRef.current.resetEmailForNewUser(selectedAccount.email);
});
};
i tweaked this demo, and i figured out a way to re-render just once like this (i delete the callback function because it runs after componentDidUpdate so it causes re-render twice):
handleChange = index => {
this.setState({ selectedIndex: index }); // set state of Parent Component (itself)
const selectedAccount = this.props.accounts[index];
this.inputRef.current.resetEmailForNewUser(selectedAccount.email); // set State of Child Component
};
i am wondering why this works and re-render just once. i thought maybe react batch both setState.
i am not sure why it's not recommended to do it like this ?
The use of the setState callback "guarantees" that this call back will only be executed after the state variables within the {} are updated:
this.setState({.... }, () => {to do only after state variables are updated})
In the second case you mentioned, there is no guarantee that this "sequential" execution will be respected. In this case you have no problems with parallelism respect of objects affected by the index parameter. However, if there are other components that depend on the "selectedIndex" state variable you could get into estrange problems depending a lot on how you structured them.

How can I set React component state based on previous state using Immer?

I'm trying to figure out how to set my React component's state based on the previous state, while also using the Immer library to allow for easier immutable state changes.
Normally, without Immer, I'd safely access the component's previous state using the updater function version of setState():
this.setState(prevState => {
prevState.counter = prevState.counter + 1;
});
However, when using the Immer library to allow for easier immutable state changes, you no longer have access to the updater function, or the prevState variable. This is fine for most operations, as the Immer draft proxy can be used for simple changes - but it doesn't work for more complex changes such as comparisons, as the proxy doesn't reflect the original state object:
this.setState(produce(draft => {
const index = draft.list.indexOf(item); // doesn't work - always returns -1
if (index > -1) {
draftState.list.splice(index, 1);
}
});
The problem is that since the draft state is a proxy, comparisons such as indexOf always fail, since the two objects are inherently different. But I don't want to just use this.state in my produce function, as the React docs are very clear that you shouldn't rely on its value when calculating the new state, and I don't want to do the state update without Immer, as complex state object changes are significantly simpler when working with a mutable proxy object.
Is there any way to access an up to date version of the component's previous state, while still using the Immer produce function in my setState calls?
Edit:
Updating to add a (simplified) example of my actual code:
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
fooBar: {
selectedOptions: []
}
};
}
handleClick(clickedOption) {
/*
if clickedOption is already selected
remove option from selectedOptions
else
add option to selectedOptions
*/
this.setState(produce(draft => {
const index = draft.fooBar.selectedOptions.indexOf(clickedOption); // The problem is here
if (index > -1) {
draft.fooBar.selectedOptions.splice(index, 1);
}
else {
draft.fooBar.selectedOptions.push(clickedOption);
}
}));
}
The problem is at the const index = ... line. If I use draft, then the indexOf function always returns -1, and if I use this.state, then I'm not guaranteed an up-to-date version of selectedOptions to compare against...
You could implement a usePrevious() hook yourself to store any previous variable:
function usePrevious(value) {
const ref = useRef();
// Store current value in ref
useEffect(() => {
ref.current = value;
}, [value]); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}

When to use setState callback argument versus directly passing the value

According to React docs, I found there are two forms of setState:
Form 1: setState({someState: someValue})
Form 2: setState((preState) => ({someState: doSomething(preState)}))
Form 1 sets state directly, form 2 sets state by using a callback function.
In the Using state correctly section, I've been told that form 1 may not be safe. Does this mean I should always to use form 2 to set state correctly? I soon noticed there is another example here which uses form 1 to update state. Is it an incorrect example?
The form 1 and form 2 may both right, but in which situations is it perfectly safe to use form 1, and in which situations should I use form 2?
I wrote a note on setState. I hope it will help you to use it properly. If you read it sincerely and understand it, you're gonna be better at using it to manage state.
setState
It's asynchronous
state = { count: 0};
increment() {
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
console.log(this.state.count) // 0
}
increment()
console.log(this.state.count); // 1
And, the final value of this.state.count will be 1 after completion of the calling incemenent()
Because React batch all calls up, and figure out the result and then efficiently make that change. Kind of this pure JavaScript code, merging where the last one wins
newState = Object.assign(
{},
firstSetStateCall,
secondSetStateCall,
thirdSetStateCall,
);
So, we can say here everything has to do with JavaScript object merging. So there's another cool way, where we pass a function in setState instead of object.
state = { count: 0};
increment() {
this.setState( (state) => { return { count: state.count + 1} } );
this.setState( (state) => { return { count: state.count + 1} } );
this.setState( (state) => { return { count: state.count + 1} } );
}
increment();
console.log(this.state.count) // 3
This time we will get 3 because earlier it was possible to merge objects but it's not possible to merge functions so it works like synchronous.
But another nice application of this method of passing parameters in this.setState is you can implement logic before returning the objects from the function
this.setState( (state) => { if(state.count === 0) return { count: state.count + 1} } );
Not only that, the function we pass inside setState takes another parameter, props.
this.setState((state, props) => { //play here })
But, the function we're passing it could grow messy by time, so what? Just make a regular JavaScript function
and pass it to the setState
this.setState(fn)
If SetState is asynchronous how we can do an operation just after the state gets updated?
setState actually takes two arguments, second one of these two is callback function, that is invoked after state is updated,
this.setState (
(state, props) => {
// code here
},
() => {console.log("updated state", this.state)}
)
The choice of form depends on whether the next state uses the value of the current state.
If the new state relies on the current state, use form 2. Otherwise use form 1.

does the state's changes with multiple this.setState calls are aggregated? [duplicate]

I know that React may perform state updates asynchronously and in batch for performance optimization. Therefore you can never trust the state to be updated after having called setState. But can you trust React to update the state in the same order as setState is called for
the same component?
different components?
Consider clicking the button in the following examples:
1. Is there ever a possibility that a is false and b is true for:
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { a: false, b: false };
}
render() {
return <Button onClick={this.handleClick}/>
}
handleClick = () => {
this.setState({ a: true });
this.setState({ b: true });
}
}
2. Is there ever a possibility that a is false and b is true for:
class SuperContainer extends React.Component {
constructor(props) {
super(props);
this.state = { a: false };
}
render() {
return <Container setParentState={this.setState.bind(this)}/>
}
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { b: false };
}
render() {
return <Button onClick={this.handleClick}/>
}
handleClick = () => {
this.props.setParentState({ a: true });
this.setState({ b: true });
}
}
Keep in mind that these are extreme simplifications of my use case. I realize that I can do this differently, e.g. updating both state params at the same time in example 1, as well as performing the second state update in a callback to the first state update in example 2. However, this is not my question, and I am only interested in if there is a well defined way that React performs these state updates, nothing else.
Any answer backed up by documentation is greatly appreciated.
I work on React.
TLDR:
But can you trust React to update the state in the same order as setState is called for
the same component?
Yes.
different components?
Yes.
The order of updates is always respected. Whether you see an intermediate state "between" them or not depends on whether you're inside in a batch or not.
In React 17 and earlier, only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.
Starting from React 18, React batches all updates by default. Note that React will never batch updates from two different intentional events (like clicks or typing) so, for example, two different button clicks will never get batched. In the rare cases that batching is not desirable, you can use flushSync.
The key to understanding this is that no matter how many setState() calls in how many components you do inside a React event handler, they will produce only a single re-render at the end of the event. This is crucial for good performance in large applications because if Child and Parent each call setState() when handling a click event, you don't want to re-render the Child twice.
In both of your examples, setState() calls happen inside a React event handler. Therefore they are always flushed together at the end of the event (and you don't see the intermediate state).
The updates are always shallowly merged in the order they occur. So if the first update is {a: 10}, the second is {b: 20}, and the third is {a: 30}, the rendered state will be {a: 30, b: 20}. The more recent update to the same state key (e.g. like a in my example) always "wins".
The this.state object is updated when we re-render the UI at the end of the batch. So if you need to update state based on a previous state (such as incrementing a counter), you should use the functional setState(fn) version that gives you the previous state, instead of reading from this.state. If you're curious about the reasoning for this, I explained it in depth in this comment.
In your example, we wouldn't see the "intermediate state" because we are inside a React event handler where batching is enabled (because React "knows" when we're exiting that event).
However, both in React 17 and earlier versions, there was no batching by default outside of React event handlers. So if in your example we had an AJAX response handler instead of handleClick, each setState() would be processed immediately as it happens. In this case, yes, you would see an intermediate state in React 17 and earlier:
promise.then(() => {
// We're not in an event handler, so these are flushed separately.
this.setState({a: true}); // Re-renders with {a: true, b: false }
this.setState({b: true}); // Re-renders with {a: true, b: true }
this.props.setParentState(); // Re-renders the parent
});
We realize it's inconvenient that the behavior is different depending on whether you're in an event handler or not. In React 18, this is no longer necessary, but before that, there was an API you can use to force batching:
promise.then(() => {
// Forces batching
ReactDOM.unstable_batchedUpdates(() => {
this.setState({a: true}); // Doesn't re-render yet
this.setState({b: true}); // Doesn't re-render yet
this.props.setParentState(); // Doesn't re-render yet
});
// When we exit unstable_batchedUpdates, re-renders once
});
Internally React event handlers are all being wrapped in unstable_batchedUpdates which is why they're batched by default. Note that wrapping an update in unstable_batchedUpdates twice has no effect. The updates are flushed when we exit the outermost unstable_batchedUpdates call.
That API is "unstable" in the sense that we will eventually remove it in some major version after 18 (either 19 or further). You safely rely on it until React 18 if you need to force batching in some cases outside of React event handlers. With React 18, you can remove it because it doesn't have any effect anymore.
To sum up, this is a confusing topic because React used to only batch inside event handlers by default. But the solution is not to batch less, it's to batch more by default. That's what we're doing in React 18.
This is actually a quite interesting question but the answer shouldn't be too complicated. There is this great article on medium that has an answer.
1) If you do this
this.setState({ a: true });
this.setState({ b: true });
I don't think that there will be a situation where a will be true and b will be false because of batching.
However, if b is dependent on a then there indeed might be a situation where you wouldn't get the expected state.
// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
After all the above calls are processed this.state.value will be 1, not 3 like you would expect.
This is mentioned in the article: setState accepts a function as its parameter
// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
This will give us this.state.value === 3
Multiple calls during the same cycle may be batched together. For example, if you attempt to increment an item quantity more than once in the same cycle, that will result in the equivalent of:
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
https://reactjs.org/docs/react-component.html
as in doc
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
it will preform the change as in queue (FIFO : First In First Out) the first call will be first to preform
In this case, it does not. I have three boxes and those state and handleClick
const [openedIndex, setOpenedIndex] = useState(-1);
const handleClic = (index) => {
console.log("opened index to test delayed state update", openedIndex);
if (index === openedIndex) {
setOpenedIndex(-1);
} else {
setOpenedIndex(index);
}
};
When I click to open and close it works:
if I get a reference to element and run $0.click() when it is opened, it closes
now what happens, if run $0.click() twice in a row, it should open and then closes but it does not
Similar post here: Updating data on changedropdown in Reactjs gets delayed
onClick works but onDoubleClick is ignored on React component

Resources