28
loading...
This website collects cookies to deliver better user experience
this.setState
or useState
do not immediately mutate the state but create a pending state transition. Accessing state immediately after calling the updater method can potentially return the old value.setState
is async, the next question that comes to mind is whether using async-await
with setState
work if we wish to access the updated state immediately after calling setState
.import React, { useState } from "react";
function AppFunctional() {
const [count, setCount] = useState(0);
const handleClick = async () => {
console.log("Functional:Count before update", count);
await setCount(count + 1);
console.log("Functional:Count post update", count);
};
return (
<div className="container">
<h1>Hello Functional Component!</h1>
<p>Press button to see the magic :)</p>
<button onClick={handleClick}>Increment</button>
{!!count && (
<div className="message">You pressed button {count} times</div>
)}
</div>
);
}
class AppClassComp extends React.Component {
state = {
count: 0
};
handleClick = async () => {
const { count } = this.state;
console.log("Class:Count before update", count);
await this.setState({ count: count + 1 });
console.log("Class:Count post update", this.state.count);
};
render() {
const { count } = this.state;
return (
<div className="container">
<h1>Hello Class Component!</h1>
<p>Press button to see the magic :)</p>
<button onClick={this.handleClick}>Increment</button>
{!!count && (
<div className="message">You pressed button {count} times</div>
)}
</div>
);
}
}
export default function App() {
return (
<div className="wrapper">
<AppFunctional />
<AppClassComp />
</div>
);
}
setState
, the updater callback is queued ahead of the resolution of await
, which basically does a Promise.resolve
with the returned value. So, it’s just a coincidence that it even works, even though setState
doesn’t return a promise. Also, even though it works there’s no guarantee that a change in implementation of setState
by React in future will retain the same behaviour.setTimeout
with sufficient delay can help us get the updated value.import React, { useState } from "react";
function AppFunctional() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log("Functional:Count before update", count);
setCount(count + 1);
setTimeout(() => {
console.log("Functional:Count post update in setTimeout", count);
}, 1000);
};
console.log("Functional:Count in render", count);
return (
<div className="container">
<h1>Hello Functional Component!</h1>
<p>Press button to see the magic :)</p>
<button onClick={handleClick}>Increment</button>
{!!count && (
<div className="message">You pressed button {count} times</div>
)}
</div>
);
}
class AppClassComp extends React.Component {
state = {
count: 0
};
handleClick = () => {
const { count } = this.state;
console.log("Class:Count before update", count);
this.setState({ count: count + 1 });
setTimeout(() => {
console.log("Class:Count post update in setTimeout", this.state.count);
}, 1000);
};
render() {
const { count } = this.state;
return (
<div className="container">
<h1>Hello Class Component!</h1>
<p>Press button to see the magic :)</p>
<button onClick={this.handleClick}>Increment</button>
{!!count && (
<div className="message">You pressed button {count} times</div>
)}
</div>
);
}
}
export default function App() {
return (
<div className="wrapper">
<AppFunctional />
<AppClassComp />
</div>
);
}
setTimeout
callback has the updated value but the functional component still doesn’t reflect the updated value.console.log(count)
placed directly inside the component shows an updated value and even though the setTimeout
callback runs after the console.log()
in render, it still shows the old value.setTimeout
, the updated values won’t be available inside its callback and the same reason applies to why async-await
also doesn’t work for state updaters in functional components.Class
and Functional
components.async-await
and setTimeout
work, the correct way to access an updated state after calling setState
is one of the following.Access the state directly in render if you just want to log or check the updated value.
Use setState
callback. `setState takes a callback as the second argument which is invoked when the state update has completed. Use this to either log or call a function with the updated state.
setState(() => {}, callback)`
Use componentDidUpdate
. A side-effect (an action) can also be performed in componentDidUpdate
after comparing the current and the previous state.
Access state directly inside the functional component. When the next render cycle is invoked, the updated value will be logged. This is useful if you only wish to log or check the updated state
Make use of useEffect
hook. You can add your state as a dependency to useEffect
and access the updated state to log or perform side-effect with the updated state values.
Use Mutation refs. This solution involves keeping a clone of state value in ref and regularly updating it. Since refs are mutated, they aren’t affected by closures and can hold updated values. This is although not directly related to accessing state after updating it but can be really useful when you want to access the updated state in an event listener or subscription callback which is only created on initial render
import React, { useState } from "react";
import "./style.scss";
export default class App extends React.Component {
state = {
count: 0
};
handleClick = () => {
const { count } = this.state;
console.log("Count before update", count);
this.setState({ count: count + 1 }, () => {
console.log("Count state in setState callback", this.state.count);
// call an action with updated state here
});
};
componentDidUpdate(_, prevState) {
if (prevState.count !== this.state.count) {
console.log("Count state in componentDidUpdate", this.state.count);
// call any side-effect here
}
}
render() {
const { count } = this.state;
console.log("Count state in render", count);
return (
<div className="container">
<h1>Hello Class Component!</h1>
<p>Press button to see the magic :)</p>
<button onClick={this.handleClick}>Increment</button>
{!!count && (
<div className="message">You pressed button {count} times</div>
)}
</div>
);
}
}
State updates in React are asynchronous because rendering is an expensive operation and making state updates synchronous may cause the browser to become unresponsive.
this.setState
provides a callback which is called when state has been updated and can be leveraged to access updated state values.
State updates in functional components are affected by closures and you only receive the updated value in the next render cycle.
For a functional component with react hooks, you can make use of useEffect
or mutationRefs
to access updated values.
If possible, try to pass the value used to update state directly as arguments to functions that are being called immediately after updating state.