I'm currently learning React and I don't fully understand why this is wrong:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
And this is correct:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
Could someone give me a real world example where i can use this "second form of setState()" that accepts a function?
This is the link
Say you have a CheckBoxComponent, whose state you initialize like this in the constructor:
this.state = {enabled: true}
You want to update its state when a user clicks on the checkbox. So you write this click handler:
function toggleCheckbox() {
this.setState({enabled: ???});
}
This kind of situation is what the second type of setState is for. The click handler should be written as:
function toggleCheckbox() {
this.setState(prevState => ({enabled: !prevState.enabled}));
}
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
1) You can just use this snippet in most situations as long as you didn't use the current state/props to calculate for the next state.
For example, this snippet only goes to fetch data from github and update to its state. We can just put an object inside this.setState().
class FetchGithub extends react.Component{
state = {
//...
};
componentDidMount() {
fetch('https://api.github.com/repos/facebook/react/commits')
.then(data => this.setState(data.json()))
.catch(err => this.setState(err));
}
}
2) But once the scenario is to use the current state/props to calculate for the next state, then you need to put instead a function to make sure that our current state gets updated already.
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
[Updated]
Since props is the argument that is been passed from this component's parent or redux's reducers, which means it takes time to process. So you also need to make sure the props is the most updated one.
Let's look at your code example again. There should be 2 components:
Parent component, that control either +1 or -1 -> AdjustComponent
Child component, just for display the result -> DisplayComponent
So the proper flow is the user click on +1/-1, AdjustComponent passes the props into DisplayComponent. And then DisplayComponent get to update its state by its current state and props sent by AdjustComponent. And show to the screen.
But what if the user click on -1 and then +1 very very very quickly or if user's computer suddenly has huge loading to deal with that affect their browser performance. So that when you use this snippet:
this.setState({
counter: state.counter + props.increment
});
The newest props(which should be +1) hasn't been received from AdjustComponent yet, but DisplayComponent updated already, using the old props.increment, which is -1 that leads to the wrong result.
Related
I have a React component that maintains state for several child components. Via componentDidMount() I am calling this function in the parent component from the child components:
change = (fieldset, field, data) => {
this.setState({
[fieldset]: {
...this.state[fieldset],
[field]: data,
}
})
}
Think form/fieldset/field for the usage pattern, but with fields calling the above function.
The problem I'm having is that I believe I'm confusing React by calling this function so many times in quick succession, because state is not updated for all but one or two items.
I've tried using Object.assign() to avoid mutating state, but for the most part state has not updated correctly even at the point where I begin to read current start.
Is this against React best practices? Is there a better way for child components to call setState in a parent component?
Since the way you update the state depends on the state itself you need to use a function instead of an object.
change = (fieldset, field, data) => {
this.setState(prevState => ({
[fieldset]: {
...prevState[fieldset],
[field]: data,
}
}))
}
Functions will be applied one after another. So you wont override any pending changes.
From docs
this.setState({quantity: this.state.quantity + 1})
this.setState({quantity: this.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 previous state, we recommend using the updater
function form, instead.
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
This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 8 months ago.
I would like to ask why my state is not changing when I do an onClick event. I've search a while ago that I need to bind the onClick function in constructor but still the state is not updating.
Here's my code:
import React from 'react';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import BoardAddModal from 'components/board/BoardAddModal.jsx';
import style from 'styles/boarditem.css';
class BoardAdd extends React.Component {
constructor(props) {
super(props);
this.state = {
boardAddModalShow: false
};
this.openAddBoardModal = this.openAddBoardModal.bind(this);
}
openAddBoardModal() {
this.setState({ boardAddModalShow: true }); // set boardAddModalShow to true
/* After setting a new state it still returns a false value */
console.log(this.state.boardAddModalShow);
}
render() {
return (
<Col lg={3}>
<a href="javascript:;"
className={style.boardItemAdd}
onClick={this.openAddBoardModal}>
<div className={[style.boardItemContainer,
style.boardItemGray].join(' ')}>
Create New Board
</div>
</a>
</Col>
);
}
}
export default BoardAdd
Your state needs some time to mutate, and since console.log(this.state.boardAddModalShow) executes before the state mutates, you get the previous value as output. So you need to write the console in the callback to the setState function
openAddBoardModal() {
this.setState({ boardAddModalShow: true }, function () {
console.log(this.state.boardAddModalShow);
});
}
setState is asynchronous. It means you can’t call it on one line and assume the state has changed on the next.
According to React docs
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.
Why would they make setState async
This is because setState alters the state and causes rerendering. This
can be an expensive operation and making it synchronous might leave
the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better
UI experience and performance.
Fortunately setState() takes a callback. And this is where we get updated state.
Consider this example.
this.setState({ name: "myname" }, () => {
//callback
console.log(this.state.name) // myname
});
So When callback fires, this.state is the updated state.
You can get mutated/updated data in callback.
For anyone trying to do this with hooks, you need useEffect.
function App() {
const [x, setX] = useState(5)
const [y, setY] = useState(15)
console.log("Element is rendered:", x, y)
// setting y does not trigger the effect
// the second argument is an array of dependencies
useEffect(() => console.log("re-render because x changed:", x), [x])
function handleXClick() {
console.log("x before setting:", x)
setX(10)
console.log("x in *line* after setting:", x)
}
return <>
<div> x is {x}. </div>
<button onClick={handleXClick}> set x to 10</button>
<div> y is {y}. </div>
<button onClick={() => setY(20)}> set y to 20</button>
</>
}
Output:
Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20
Live version
Since setSatate is a asynchronous function so you need to console the state as a callback like this.
openAddBoardModal(){
this.setState({ boardAddModalShow: true }, () => {
console.log(this.state.boardAddModalShow)
});
}
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props. For instance, suppose we wanted to increment a value in state by props.step:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
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.
Check this for more information.
In your case you have sent a request to update the state. It takes time for React to respond. If you try to immediately console.log the state, you will get the old value.
The above solutions don't work for useState hooks.
One can use the below code
setState((prevState) => {
console.log(boardAddModalShow)
// call functions
// fetch state using prevState and update
return { ...prevState, boardAddModalShow: true }
});
This callback is really messy. Just use async await instead:
async openAddBoardModal(){
await this.setState({ boardAddModalShow: true });
console.log(this.state.boardAddModalShow);
}
If you want to track the state is updating or not then the another way of doing the same thing is
_stateUpdated(){
console.log(this.state. boardAddModalShow);
}
openAddBoardModal(){
this.setState(
{boardAddModalShow: true},
this._stateUpdated.bind(this)
);
}
This way you can call the method "_stateUpdated" every time you try to update the state for debugging.
Although there are many good answers, if someone lands on this page searching for alternative to useState for implementing UI components like Navigation drawers which should be opened or closed based on user input, this answer would be helpful.
Though useState seems handy approach, the state is not set immediately and thus, your website or app looks laggy... And if your page is large enough, react is going to take long time to compute what all should be updated upon state change...
My suggestion is to use refs and directly manipulate the DOM when you want UI to change immediately in response to user action.
Using state for this purspose is really a bad idea in case of react.
setState() is asynchronous. The best way to verify if the state is updating would be in the componentDidUpdate() and not to put a console.log(this.state.boardAddModalShow) after this.setState({ boardAddModalShow: true }) .
according to React Docs
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
According to React Docs
React does not guarantee that the state changes are applied immediately.
This makes reading this.state right after calling setState() a potential pitfall and can potentially return the existing value due to async nature .
Instead, use componentDidUpdate or a setState callback that is executed right after setState operation is successful.Generally we recommend using componentDidUpdate() for such logic instead.
Example:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 1
};
}
componentDidUpdate() {
console.log("componentDidUpdate fired");
console.log("STATE", this.state);
}
updateState = () => {
this.setState(
(state, props) => {
return { counter: state.counter + 1 };
});
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.updateState}>Update State</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
this.setState({
isMonthFee: !this.state.isMonthFee,
}, () => {
console.log(this.state.isMonthFee);
})
when i was running the code and checking my output at console it showing the that it is undefined.
After i search around and find something that worked for me.
componentDidUpdate(){}
I added this method in my code after constructor().
check out the life cycle of react native workflow.
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
Yes because setState is an asynchronous function. The best way to set state right after you write set state is by using Object.assign like this:
For eg you want to set a property isValid to true, do it like this
Object.assign(this.state, { isValid: true })
You can access updated state just after writing this line.
So there is this simplified structure in my app:
In component i have :
handlePageClick(data) {
this.props.onChangePage(data.selected + 1);
this.props.onSearchSomething(this.props.PM);
}
In my Container :
const mapDispatchToProps = dispatch => ({
onUpdateForm: (propPath, val) => {
dispatch(updatePMForm(propPath, val));
},
onSearchSomething: (payload) => {
// TODO: process the PM state here when integrating with the server in order to extract
// the proper payload out of the state object
dispatch(searchSomething(15, payload));
},
onChangePage: (pageNumber) => {
dispatch(changePage(pageNumber));
},
});
So when i trigger pageClick, and onChangePage returns newState, in mapDispatchToProps dispatch(searchSomething(15, payload)) receive previous State in payload, that hasn't been updated yet.
You should use componentDidUpdate or componentWillUpdate and diff previous state with current one and then call this.props.onSearchSomething with updated value from the store rather than call it one after another in handlePageClick. Why is that? State changes in a component are potentially asynchronous and almost in all cases there's no way that the component's props will be updated between this.props.onChangePage and this.props.onSearchSomething calls. Please read this issue for better understanding what I'm talking about.
But to help you out a bit some time ago I created a small utility map-props-changes-to-callbacks to assign callbacks in your components to specific changes in redux store. Perhaps that can be helpful in your case and you can write your code like that:
handlePageClick(data) {
this.props.onChangePage(data.selected + 1)
}
onPageChanged() {
this.props.onSearchSomething(this.props.PM)
}
Also, you should consider moving your logic to a single action so you can just dispatch one action and get the search result in return.
I have App component, which loads JSON data from the server.
And after data is loaded, I update state of child component.
Now my function looks like this:
componentDidMount() {
setTimeout(()=> {
if (this.state.users.length !== this.props.users.length) {
this.setState({users: this.props.users});
this.setState({tasks: this.getTasksArray()});
}, 500);
}
I use setTimeout to wait if data is loaded and sent to child. But I'm sure, it is not the best way
May be, it's better to use redux instead of setTimeout.
Parent component loads data:
componentWillMount() {
var _url = "/api/getusers/";
fetch(_url)
.then((response) => response.json())
.then(users => {
this.setState({ users });
console.log("Loaded data:", users);
});
}
Parent sends props with:
<AllTasks users={this.state.users} />
So, my question is: what is the best way to watch changes in child component?
I mean in this particular situation.
Yes, this is not the correct way because api calls will be asynchronous and we don't know how much time it will take.
So instead of using setTimeout, use componentWillReceiveProps lifecycle method in child component, it will get called whenever you change props values (state of parent component).
Like this:
componentWillReceiveProps(newProps){
this.setState({
users: newProps.users,
tasks: this.getTasksArray()
})
}
One more thing, don't call setState multiple times within a function because setState will trigger re-rendering so first do all the calculations then do setState in the last and update all the values in one call.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
Update:
You are calling a method from cWRP method and using the props values inside that method, this.props will have the updated values after this lifecycle method only. So you need to pass the newProps values as a parameter in this function and use that instead of this.props.
Like this:
componentWillReceiveProps(newProps){
this.setState({
users: newProps.users,
tasks: this.getTasksArray(newProps)
})
}
getTasksArray(newProps){
//here use newProps instead of this.props
}
Check this answer for more details: componentWillRecieveProps method is not working properly: ReactJS
I found the problem.
I have getTasksArray() function that depends of props and uses this.props.users.
So, when I update state like this:
this.setState({
users: newProps.users,
tasks: this.getTasksArray()
})
getTasksArray() function uses empty array.
but when I split it to 2 lines and add setTimeout(fn, 0) like this:
this.setState({users: newProps.users});
setTimeout(()=> { this.setState({ tasks: this.getTasksArray() }, 0)
getTasksArray() function uses array that is already updated.
setTimeout(fn, 0) makes getTasksArray() to run after all other (even if I set timeout to 0 ms).
Here is the screenshot of console.log's without setTimeout:
And here is the screenshot with setTimeout: