reactjs - When to use an object in `setState` instead of using functional `setState`? - Stack Overflow
Understanding calling setState with a function instead of an object
Why does passing a function with your previous state as a parameter ensure that state is updated before the code is executed?
reactjs - Should a function always be used for a setState that gets passed an array or object? - Stack Overflow
The norm is to use an object, it's also less code and easier to read. The only reason u want to use a function setState is when u need to access the previous state.
consider the following example
// you have a switch state
state = {
checked: false
}
// .. and later in an onChange method
onChange = () => {
const {checked} = this.state;
// this is problematic because react works in an async fashion, so it could be that when this function was called checked was false, however it was only executed later when checked was actually true
this.setState({checked: !checked}
// on the other hand this form is safe, even if things were happening async, you are safe because you act on the most updated state
this.setState(prvState => {...prvState, checked: !prvState.checked})
}
In Javascript, a function is an object.
Therefore, technically, there is no difference between
a function being passed as the argument to setState and
an object being passed as the argument for the setState method.
However, a function may be used instead of an object for the purpose of formatting or for performing temporary calculations before updating the state attributes.
Example use cases for function in place of an object:
- Rounding-off decimal places
- Conversion of units (Celcius --> fahrenheit, KG to lb, etc)
- Compute derived information (max, min, average, etc)
Hey,
I'm paraphrasing my understanding of calling setState with an object vs with a function. I've highlighted the bits on which I seek clarity.
The problem: setState calls are async for performance reasons. Which means that when you have a call like -
this.setState({ default: 'some default value' });
this.setState({ counter: this.state.counter + 1 });
this.setState({ counter: this.state.counter + 2 });
this.setState({ counter: this.state.counter + 3 });
You might expect a total increment of 6, but only the last setState call with an increment of 3 will actually reflect (And not necessarily immediately; but when it happens, it'll be 3). It is worth noting that this is a problem only when our current sate depends on the previous state because we aren't sure if our current state is the latest one, there might be some async update yet to be performed.
This result of 3 is because React will group multiple calls and create one final updated state object to be set, so that there will be only one final render, instead of separately making each state update and a respective render each time. Similarly, if we had set default multiple times, the last one would have been the one reflected.
So the new state object created for each setState will first have set to { counter: this.state.counter + 1 }, and then counter will be overridden by { counter: this.state.counter + 2 } and finally overridden again by { counter: this.state.counter + 3 }.
To solve this, we pass setState a function rather than an object.
this.setState((prevState, currentProps) => ({ counter: prevState.counter + 1 }));
this.setState((prevState, currentProps) => ({ counter: prevState.counter + 2 }));
this.setState((prevState, currentProps) => ({ counter: prevState.counter + 3 }));What I am confused about
-
Assuming an initial counter value of
0, the function passedsetStatewill resolve to{ counter: 0 + 1 }on the first call,{ counter: 1 + 2 }in the second and{ counter: 3 + 3 }in the third. And then just like previously, the final state object, which will be a merge of all thesetStatecalls' objects, the last set to updatecounterwill override the previous ones - but having access to an updated previous state avoids any discrepancies. Correct? -
Does this make the bits that pass a function to
setStatesynchronous? (Because otherwiseprevStatewon't necessarily be updated for the subsequent `setState` calls that also pass a function and accessprevState) -
As a corollary of 2, if there are calls to setState like this -
this.setState({ counter: this.state.counter + 1 });
this.setState({ counter: this.state.counter + 2 });
this.setState((prevState, currentProps) => ({ counter: prevState.counter + 3 }));
this.setState((prevState, currentProps) => ({ counter: prevState.counter + 4 }));
this.setState({ counter: this.state.counter + 5 });
Will call 1, 2 and 5 happen asynchronously in a batch with 5 overriding 1 and 2 while 3 and 4 will be sync calls giving the counter 5 a state of 7 in addition to the initial value of counter?
I'd be grateful if someone could please validate my understanding.
TIA
EDIT: grammar and formatting and added point 3.
Currently doing Maximilian Schwarzmüller's Udemy React course, and there is a lesson that seems important but which I don't quite understand the logic behind.
He said that whenever you are updating state in a way that depends on the previous state (for example, updating a state object where you're only changing one value, or updating a counter with state) you should NOT simply call the previous state variable in your setState function, but instead you should call another function where you pass the previous state in as a parameter, and return your new value. He said that this will ensure that your previous state is up to date before running the code.
For example, this is incorrect, because the value of counter could be out of date in the clickHandler function:
const [counter, setCounter] = useState(0);
const clickHandler = () => {
setCounter(counter + 1)
};and instead, you should do this:
const [counter, setCounter] = useState(0);
const clickHandler = () => {
setCounter((prevState) => {
return (prevState + 1)
})
}I can certainly just memorize this principle, but I don't understand exactly why it works. Can somebody explain why calling another function updates the state? I guess I don't really understand the "prevState" variable or how it is being assigned the value of the most updated state.
I had a little discussion with my colleagues, who are frontend developers like me, about passing set functions from a parent component to its children. They think that when you share the state from useState hooks between components, passing callback is a good practice, but passing the set function is well too.
It is good, they say, to write code like this:
export const ModalController = () => {
const [open, setOpen] = useState(false);
return (
{open && <Modal onClose={() => setOpen(false)}/>}
<Button onClick={() => setOpen(true)}>OPEN MODAL</Button>
);
};But if you should change different fragments of the parent's state on various actions in a child component, passing the set function as is will be better. Like there:
interface StateType {
name: string;
description: string;
birthDate: Date;
phoneNumber: string;
}
export const ParentComponent = () => {
const [complexState, setComplexState] = useState<StateType>();
return <ComplexForm values={complexState} changeValues={setComplexState} />;
};
I don't like this approach because it breaks components' encapsulation. I think, to create a good component, you should keep in this component its state and actions which it is responsible for. Your component can provide some data and actions to its children, but the children shouldn't know anything about their parent's structure. When you pass the set function to the children, you give them direct control over the state of the parent. In my opinion, it leads to some problems:
-
If you have several child components receiving the
setfunction, you need to write something likeonClick={setState(newValues)}or evenonClick={(key, value) => setState((prev) => ({ ...prev, [key]: value }))}in each of them. If you have only one component, you should think about code flexibility and imagine that you could have more child components in the future. -
If you'll change the parent's state structure in the future (e.g. when you'll refactor your code), it will be necessary to change the parent's state structure in the components that receive the
setfunction. -
You don't restrict the parent component's state changing in its children. I think, there can be a specific case when it can lead to some unexpected bugs (because other programmers or the future you change the parent component's state in a way it shouldn't be changed).
-
If you use TS, you need to write a complex type such as
React.Dispatch<React.SetStateAction<StateType>>in the props interface of every child component receiving thesetfunction. It is not flexible and less readable than just() => voidor(key: keyof StateType, value: SomeStateValue) => void. -
This approach is bad for your mindset after all. Maybe it is impossible to write the perfect code, but React developers in my opinion should design as flexible and independent components as possible when they have enough resources (e.g. time before the deadline) to do it.
And what about the case when the different fragments of the parent's state should be changed on various actions in a child component? You can handle it with the callback like this:
type CallbackInput = Partial<StateType>;
const callback = (input: CallbackInput) => {
setState((prev) => Object.assign({ ...prev, ...input }));
};
So, I believe, it is always better to use a callback instead of a set function when you pass your state to children components. What do you think about it? Are my arguments valid for you or you can challenge them?
In the first case when you add todos as a dependency to useCallback, the function will be recreated again each time you call it since it is setting todos state itself and hence will not be optimized for memoization
In the second case, you are using the callback version os state updater which essentially will provide you the previous state as an argument to callback and the function will be only created once.
This should be the preferred approach
To know more about the advantages of functional setState, please check the linked post:
1) why does it render components quicker?
- the performance gain of second code is not come from functional
setState() - you use
useCallback()to memoize the functiononRemove(), so it won't be created every time the componentre-render. - you pass no dependencies, so the function
onRemove()will be created only once - when the component mounts
2) What are the other advantages of using functional setState()?
functional
setState()is used to escapeclosure
- use static state
import React, { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
console.log('render');
useEffect(() => {
console.log('componentDidUpdate')
const id = setInterval(() => {
setCount(count) // count is always 0 because the variable count is closured
}, 1000);
return () => {
clearInterval(id);
console.log('clean');
}
}, []); // run only once when the component mounts
return (
<h1>
{count}
</h1>
)
}
export default Counter;
- use functional
setState()to read fresh state
import React, { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
console.log('render');
useEffect(() => {
console.log('componentDidUpdate')
const id = setInterval(() => {
setCount(count => count + 1); // read fresh value of count
}, 1000);
return () => {
clearInterval(id);
console.log('clean');
}
}, []); // run only once when the component mounts
return (
<h1>
{count}
</h1>
)
}
export default Counter;
Reference
- A Complete Guide to useEffect
First things first, in your case the two syntaxes are entirely different, what you might be looking for is the difference between
this.setState({pictures: this.state.picture.concat(pics)})
and
this.setState(prevState => ({
pictures: prevState.pictures.concat(pics)
}))
To understand why the second method is a preferred one,you need to understand what React does with setState() internally.
React will first merge the object you passed to setState() into the current state. Then it will start that reconciliation thing. Because of the calling setState() might not immediately update your state.
React may batch multiple setState() calls into a single update for better performance.
Consider the simple case, to understand this, in your function you might call setState() more than once like:
myFunction = () => {
...
this.setState({pictures: this.state.picture.concat(pics1)})
this.setState({pictures: this.state.picture.concat(pics1)})
this.setState({pictures: this.state.picture.concat(pics1)})
...
}
which isn't a valid use case in a simple app but as the app gets complex, multiple setState() calls may be happening from multiple places, doing the same thing.
So now to perform an efficient update, React does the batching thing by extracting all the objects passed to each setState() call, merges them together to form a single object, then uses that single object to do setState(). According to the setState() documentation:
This form of
setState()is also asynchronous, and 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}, ... )Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the current state, we recommend using the updater function form, instead:
this.setState((state) => { return {quantity: state.quantity + 1}; });For more detail, see:
- State and Lifecycle guide
- In depth: When and why are
setState()calls batched?- In depth: Why isn’t
this.stateupdated immediately?—
setState()- Other APIs - React.Component – React.
So if any of the objects contains the same key, the value of the key of the last object with same key is stored. And hence the update only happens once with the last value.
Demo Codesandbox
TL;DR: Functional setState is mainly helpful to expect correct state update when multiple setStates are triggered in a short interval of time where as conventional setState does reconciliation and could lead to unexpected state.
Please check this wonderful article on Functional setState to get a clear understanding
And here is the WORKING DEMO
Firstly, you are trying to do different things in each of your cases, you are assigning pics in one and concatenating pics in another.
Lets say this.state.pictures contain [1, 2, 3] and pics contain [4, 5, 6]
this.setState({pictures: pics})
In the above case this.state.pictures will contain the pics i.e this.state.pictures = [4, 5, 6]
this.setState(prevState => ({
pictures: prevState.pictures.concat(pics)
}))
Whereas in the above snippet, this.state.pictures will contain the previous pictures + pics i.e this.state.pictures = [1, 2, 3, 4, 5, 6]
Either way you can tweak it to meet your API needs, if it's like a paginated response the you are better off concatenating the results on each request, else if you get the whole array each time just assign the whole array.
Conventional setState VS Functional setState
Now, your question mainly is probably whether to use setState with an object or with a function.
People use both of them for different reasons, now the functional setState is said to be safe because React does something called a reconciliation process where in it merges multiple objects inside setState when triggered frequently or concurrently and applies the changes in one shot, kind of like a batch process. This could potentially lead to some unexpected results in scenarios like below.
EXAMPLE:
increaseScoreBy3 () {
this.setState({score : this.state.score + 1});
this.setState({score : this.state.score + 1});
this.setState({score : this.state.score + 1});
}
As you would expect the score to be incremented by 3 but what React does is merge all the objects and update the score only once i.e incrementing it by 1
This is one of the cases where functional callback shines because reconciliation does not happen on functions and executing the below code will do things as expected i.e update score by 3
increaseScoreBy3 () {
this.setState(prevState => ({ score: prevState.score + 1 }));
this.setState(prevState => ({ score: prevState.score + 1 }));
this.setState(prevState => ({ score: prevState.score + 1 }));
}
const [state, updateState] = useState(0) updateState(prevState => prevState+1) //Would output to 1
Why is this the recommended way to update states using useState?
updateState(prevState+1) //Would also output 1
Why not this?
I've tried searching for an explanation for this and this is the only one I got
const [state, updateState] = useState(0) updateState(prevState+1) //Would also output 1 updateState(prevState+1) //Wouldn't output 2, still outputs 1
The explanation I got from this was that the second updateState still references for state with the value of 0
const [state, updateState] = useState(0) updateState(prevState => prevState+1) //Would output to 1 updateState(prevState => prevState+1) //Would output to 2
But why does this work? What's the underlying mechanism difference between the two?
Thanks!