Component not re-rendering after state change [duplicate] - javascript

So I have this:
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tittal'); //outputs correct total
setTimeout(() => {
this.setState({ dealersOverallTotal: total });
}, 10);
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1'); //outputs incorrect total
newDealersDeckTotal is just an array of numbers [1, 5, 9] e.g.
however this.state.dealersOverallTotal does not give the correct total but total does? I even put in a timeout delay to see if this solved the problem.
any obvious or should I post more code?

setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in the callback of the setState() method. It is executed after the state change is complete:
this.setState({ dealersOverallTotal: total }, () => {
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});

In case of hooks, you should use useEffect hook.
const [fruit, setFruit] = useState('');
setFruit('Apple');
useEffect(() => {
console.log('Fruit', fruit);
}, [fruit])

setState is asynchronous. You can use callback method to get updated state.
changeHandler(event) {
this.setState({ yourName: event.target.value }, () =>
console.log(this.state.yourName));
}

Using async/await
async changeHandler(event) {
await this.setState({ yourName: event.target.value });
console.log(this.state.yourName);
}

The setState is asynchronous in react, so to see the updated state in console use the callback as shown below (Callback function will execute after the setState update)
this.setState({ email: 'test#example.com' }, () => {
console.log(this.state.email)
)}

I had an issue when setting react state multiple times (it always used default state). Following this react/github issue worked for me
const [state, setState] = useState({
foo: "abc",
bar: 123
});
// Do this!
setState(prevState => {
return {
...prevState,
foo: "def"
};
});
setState(prevState => {
return {
...prevState,
bar: 456
};
});

The setState() operation is asynchronous and hence your console.log() will be executed before the setState() mutates the values and hence you see the result.
To solve it, log the value in the callback function of setState(), like:
setTimeout(() => {
this.setState({dealersOverallTotal: total},
function(){
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
}, 10)

If you work with funcions you need to use UseEffect to deal with setState's asynchrony (you can't use the callback as you did when working with classes). An example:
import { useState, useEffect } from "react";
export default function App() {
const [animal, setAnimal] = useState(null);
function changeAnimal(newAnimal) {
setAnimal(newAnimal);
// here 'animal' is not what you would expect
console.log("1", animal);
}
useEffect(() => {
if (animal) {
console.log("2", animal);
}
}, [animal]);
return (
<div className="App">
<button onClick={() => changeAnimal("dog")} />
</div>
);
}
First console.log returns null, and the second one returns 'dog'

just add componentDidUpdate(){} method in your code, and it will work.
you can check the life cycle of react native here:
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8

As well as noting the asynchronous nature of setState, be aware that you may have competing event handlers, one doing the state change you want and the other immediately undoing it again. For example onClick on a component whose parent also handles the onClick. Check by adding trace. Prevent this by using e.stopPropagation.

I had the same situation with some convoluted code, and nothing from the existing suggestions worked for me.
My problem was that setState was happening from callback func, issued by one of the components. And my suspicious is that the call was occurring synchronously, which prevented setState from setting state at all.
Simply put I have something like this:
render() {
<Control
ref={_ => this.control = _}
onChange={this.handleChange}
onUpdated={this.handleUpdate} />
}
handleChange() {
this.control.doUpdate();
}
handleUpdate() {
this.setState({...});
}
The way I had to "fix" it was to put doUpdate() into setTimeout like this:
handleChange() {
setTimeout(() => { this.control.doUpdate(); }, 10);
}
Not ideal, but otherwise it would be a significant refactoring.

Related

React state does not update immediately [duplicate]

I'm working on a todo application. This is a very simplified version of the offending code. I have a checkbox:
<p><input type="checkbox" name="area" checked={this.state.Pencil} onChange={this.checkPencil}/> Writing Item </p>
Here's the function that calls the checkbox:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
});
this.props.updateItem(this.state);
}
updateItem is a function that's mapped to dispatch to redux
function mapDispatchToProps(dispatch){
return bindActionCreators({ updateItem}, dispatch);
}
My problem is that when I call the updateItem action and console.log the state, it is always 1 step behind. If the checkbox is unchecked and not true, I still get the state of true being passed to the updateItem function. Do I need to call another function to force the state to update?
You should invoke your second function as a callback to setState, as setState happens asynchronously. Something like:
this.setState({pencil:!this.state.pencil}, myFunction)
However in your case since you want that function called with a parameter you're going to have to get a bit more creative, and perhaps create your own function that calls the function in the props:
myFunction = () => {
this.props.updateItem(this.state)
}
Combine those together and it should work.
Calling setState() in React is asynchronous, for various reasons (mainly performance). Under the covers React will batch multiple calls to setState() into a single state mutation, and then re-render the component a single time, rather than re-rendering for every state change.
Fortunately, the solution is rather simple - setState accepts a callback parameter:
checkPencil: () => {
this.setState(previousState => ({
pencil: !previousState.pencil,
}), () => {
this.props.updateItem(this.state);
});
}
On Ben Hare's answer, If someone wants to achieve the same using React Hooks I have added sample code below.
import React, { useState, useEffect } from "react"
let [myArr, setMyArr] = useState([1, 2, 3, 4]) // the state on update of which we want to call some function
const someAction = () => {
let arr = [...myArr]
arr.push(5) // perform State update
setMyArr(arr) // set new state
}
useEffect(() => { // this hook will get called every time myArr has changed
// perform some action every time myArr is updated
console.log('Updated State', myArr)
}, [myArr])
When you're updating your state using a property of the current state, React documentation advise you to use the function call version of setState instead of the object.
So setState((state, props) => {...}) instead of setState(object).
The reason is that setState is more of a request for the state to change rather than an immediate change. React batches those setState calls for performance improvement.
Meaning the state property you're checking might not be stable.
This is a potential pitfall to be aware of.
For more info see documentation here: https://facebook.github.io/react/docs/react-component.html#setstate
To answer your question, i'd do this.
checkPencil(){
this.setState((prevState) => {
return {
pencil: !prevState.pencil
};
}, () => {
this.props.updateItem(this.state)
});
}
It's because it happens asynchronously, so means in that time might not get updated yet...
According to React v.16 documentation, you need to use a second form of setState() that accepts a function rather than an object:
State Updates May Be Asynchronous
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.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
First set your value. after proceed your works.
this.setState({inputvalue: e.target.value}, function () {
this._handleSubmit();
});
_handleSubmit() {
console.log(this.state.inputvalue);
//Do your action
}
I used both rossipedia's and Ben Hare's suggestions and did the following:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
}, this.updatingItem);
}
updatingItem(){
this.props.updateItem(this.state)
}
Ben has a great answer for how to solve the immediate issue, however I would also advise to avoid duplicating state
If a state is in redux, your checkbox should be reading its own state from a prop or store instead of keeping track of the check state in both its own component and the global store
Do something like this:
<p>
<input
type="checkbox"
name="area" checked={this.props.isChecked}
onChange={this.props.onChange}
/>
Writing Item
</p>
The general rule is that if you find a state being needed in multiple places, hoist it up to a common parent (not always redux) to maintain only having a single source of truth
try this
this.setState({inputvalue: e.target.value}, function () {
console.log(this.state.inputvalue);
this.showInputError(inputs[0].name);
});
showInputError function for validation if using any forms
As mentioned above setState() is asynchronous in nature. I solved this issue simply using async await.
Here's an example for refernce:
continue = async (e) => {
e.preventDefault();
const { values } = this.props;
await this.setState({
errors: {}
});
const emailValidationRegex = /^(([^<>()\[\]\.,;:\s#\"]+(\.[^<>()\[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i;
if(!emailValidationRegex.test(values.email)){
await this.setState((state) => ({
errors: {
...state.errors,
email: "enter a valid email"
}
}));
}
}
You can also update the state twice like below and make the state update immediately, this worked for me:
this.setState(
({ app_id }) => ({
app_id: 2
}), () => {
this.setState(({ app_id }) => ({
app_id: 2
}))
} )
Here is React Hooks based solution.
Since React useState updates state asynchronously, check them in the useEffect hook if you need to see these changes.
Make sure to give the initialState in the useState each time using a variable. Like line 1 and 2. If I did not give anything in it it would work on double click to fill the errors variable.
1) let errorsArray = [];
2) let [errors, setErrors] = useState(errorsArray);
3) let [firstName, setFirstName] = useState('');
4) let [lastName, setLastName] = useState('');
let [gender, setGender] = useState('');
let [email, setEmail] = useState('');
let [password, setPassword] = useState('');
const performRegister = () => {
console.log('firstName', isEmpty(firstName));
if (isEmpty(firstName)) {
console.log('first if statement');
errorsArray.push({firstName: 'First Name Cannot be empty'});
}
if (isEmpty(lastName)) {
errorsArray.push({lastName: 'Last Name Cannot be empty'});
}
if (isEmpty(gender)) {
errorsArray.push({gender: 'Gender Cannot be empty'});
}
if (isEmpty(email)) {
errorsArray.push({email: 'Email Cannot be empty'});
}
if (isEmpty(password)) {
errorsArray.push({password: 'Password Cannot be empty'});
}
console.log('outside ERRORS array :::', errorsArray);
setErrors(errorsArray);
console.log('outside ERRORS :::', errors);
if (errors.length > 0) {
console.log('ERROR exists');
}
};

setState not uodating on first call [duplicate]

So I have this:
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tittal'); //outputs correct total
setTimeout(() => {
this.setState({ dealersOverallTotal: total });
}, 10);
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1'); //outputs incorrect total
newDealersDeckTotal is just an array of numbers [1, 5, 9] e.g.
however this.state.dealersOverallTotal does not give the correct total but total does? I even put in a timeout delay to see if this solved the problem.
any obvious or should I post more code?
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in the callback of the setState() method. It is executed after the state change is complete:
this.setState({ dealersOverallTotal: total }, () => {
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
In case of hooks, you should use useEffect hook.
const [fruit, setFruit] = useState('');
setFruit('Apple');
useEffect(() => {
console.log('Fruit', fruit);
}, [fruit])
setState is asynchronous. You can use callback method to get updated state.
changeHandler(event) {
this.setState({ yourName: event.target.value }, () =>
console.log(this.state.yourName));
}
Using async/await
async changeHandler(event) {
await this.setState({ yourName: event.target.value });
console.log(this.state.yourName);
}
The setState is asynchronous in react, so to see the updated state in console use the callback as shown below (Callback function will execute after the setState update)
this.setState({ email: 'test#example.com' }, () => {
console.log(this.state.email)
)}
I had an issue when setting react state multiple times (it always used default state). Following this react/github issue worked for me
const [state, setState] = useState({
foo: "abc",
bar: 123
});
// Do this!
setState(prevState => {
return {
...prevState,
foo: "def"
};
});
setState(prevState => {
return {
...prevState,
bar: 456
};
});
The setState() operation is asynchronous and hence your console.log() will be executed before the setState() mutates the values and hence you see the result.
To solve it, log the value in the callback function of setState(), like:
setTimeout(() => {
this.setState({dealersOverallTotal: total},
function(){
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
}, 10)
If you work with funcions you need to use UseEffect to deal with setState's asynchrony (you can't use the callback as you did when working with classes). An example:
import { useState, useEffect } from "react";
export default function App() {
const [animal, setAnimal] = useState(null);
function changeAnimal(newAnimal) {
setAnimal(newAnimal);
// here 'animal' is not what you would expect
console.log("1", animal);
}
useEffect(() => {
if (animal) {
console.log("2", animal);
}
}, [animal]);
return (
<div className="App">
<button onClick={() => changeAnimal("dog")} />
</div>
);
}
First console.log returns null, and the second one returns 'dog'
just add componentDidUpdate(){} method in your code, and it will work.
you can check the life cycle of react native here:
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
As well as noting the asynchronous nature of setState, be aware that you may have competing event handlers, one doing the state change you want and the other immediately undoing it again. For example onClick on a component whose parent also handles the onClick. Check by adding trace. Prevent this by using e.stopPropagation.
I had the same situation with some convoluted code, and nothing from the existing suggestions worked for me.
My problem was that setState was happening from callback func, issued by one of the components. And my suspicious is that the call was occurring synchronously, which prevented setState from setting state at all.
Simply put I have something like this:
render() {
<Control
ref={_ => this.control = _}
onChange={this.handleChange}
onUpdated={this.handleUpdate} />
}
handleChange() {
this.control.doUpdate();
}
handleUpdate() {
this.setState({...});
}
The way I had to "fix" it was to put doUpdate() into setTimeout like this:
handleChange() {
setTimeout(() => { this.control.doUpdate(); }, 10);
}
Not ideal, but otherwise it would be a significant refactoring.

ReactJS Hooks - Set State based on another State doesn't render immediately [duplicate]

So I have this:
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tittal'); //outputs correct total
setTimeout(() => {
this.setState({ dealersOverallTotal: total });
}, 10);
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1'); //outputs incorrect total
newDealersDeckTotal is just an array of numbers [1, 5, 9] e.g.
however this.state.dealersOverallTotal does not give the correct total but total does? I even put in a timeout delay to see if this solved the problem.
any obvious or should I post more code?
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in the callback of the setState() method. It is executed after the state change is complete:
this.setState({ dealersOverallTotal: total }, () => {
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
In case of hooks, you should use useEffect hook.
const [fruit, setFruit] = useState('');
setFruit('Apple');
useEffect(() => {
console.log('Fruit', fruit);
}, [fruit])
setState is asynchronous. You can use callback method to get updated state.
changeHandler(event) {
this.setState({ yourName: event.target.value }, () =>
console.log(this.state.yourName));
}
Using async/await
async changeHandler(event) {
await this.setState({ yourName: event.target.value });
console.log(this.state.yourName);
}
The setState is asynchronous in react, so to see the updated state in console use the callback as shown below (Callback function will execute after the setState update)
this.setState({ email: 'test#example.com' }, () => {
console.log(this.state.email)
)}
I had an issue when setting react state multiple times (it always used default state). Following this react/github issue worked for me
const [state, setState] = useState({
foo: "abc",
bar: 123
});
// Do this!
setState(prevState => {
return {
...prevState,
foo: "def"
};
});
setState(prevState => {
return {
...prevState,
bar: 456
};
});
The setState() operation is asynchronous and hence your console.log() will be executed before the setState() mutates the values and hence you see the result.
To solve it, log the value in the callback function of setState(), like:
setTimeout(() => {
this.setState({dealersOverallTotal: total},
function(){
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
}, 10)
If you work with funcions you need to use UseEffect to deal with setState's asynchrony (you can't use the callback as you did when working with classes). An example:
import { useState, useEffect } from "react";
export default function App() {
const [animal, setAnimal] = useState(null);
function changeAnimal(newAnimal) {
setAnimal(newAnimal);
// here 'animal' is not what you would expect
console.log("1", animal);
}
useEffect(() => {
if (animal) {
console.log("2", animal);
}
}, [animal]);
return (
<div className="App">
<button onClick={() => changeAnimal("dog")} />
</div>
);
}
First console.log returns null, and the second one returns 'dog'
just add componentDidUpdate(){} method in your code, and it will work.
you can check the life cycle of react native here:
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
As well as noting the asynchronous nature of setState, be aware that you may have competing event handlers, one doing the state change you want and the other immediately undoing it again. For example onClick on a component whose parent also handles the onClick. Check by adding trace. Prevent this by using e.stopPropagation.
I had the same situation with some convoluted code, and nothing from the existing suggestions worked for me.
My problem was that setState was happening from callback func, issued by one of the components. And my suspicious is that the call was occurring synchronously, which prevented setState from setting state at all.
Simply put I have something like this:
render() {
<Control
ref={_ => this.control = _}
onChange={this.handleChange}
onUpdated={this.handleUpdate} />
}
handleChange() {
this.control.doUpdate();
}
handleUpdate() {
this.setState({...});
}
The way I had to "fix" it was to put doUpdate() into setTimeout like this:
handleChange() {
setTimeout(() => { this.control.doUpdate(); }, 10);
}
Not ideal, but otherwise it would be a significant refactoring.

Why my setstate gives me undefined? React [duplicate]

I'm working on a todo application. This is a very simplified version of the offending code. I have a checkbox:
<p><input type="checkbox" name="area" checked={this.state.Pencil} onChange={this.checkPencil}/> Writing Item </p>
Here's the function that calls the checkbox:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
});
this.props.updateItem(this.state);
}
updateItem is a function that's mapped to dispatch to redux
function mapDispatchToProps(dispatch){
return bindActionCreators({ updateItem}, dispatch);
}
My problem is that when I call the updateItem action and console.log the state, it is always 1 step behind. If the checkbox is unchecked and not true, I still get the state of true being passed to the updateItem function. Do I need to call another function to force the state to update?
You should invoke your second function as a callback to setState, as setState happens asynchronously. Something like:
this.setState({pencil:!this.state.pencil}, myFunction)
However in your case since you want that function called with a parameter you're going to have to get a bit more creative, and perhaps create your own function that calls the function in the props:
myFunction = () => {
this.props.updateItem(this.state)
}
Combine those together and it should work.
Calling setState() in React is asynchronous, for various reasons (mainly performance). Under the covers React will batch multiple calls to setState() into a single state mutation, and then re-render the component a single time, rather than re-rendering for every state change.
Fortunately, the solution is rather simple - setState accepts a callback parameter:
checkPencil: () => {
this.setState(previousState => ({
pencil: !previousState.pencil,
}), () => {
this.props.updateItem(this.state);
});
}
On Ben Hare's answer, If someone wants to achieve the same using React Hooks I have added sample code below.
import React, { useState, useEffect } from "react"
let [myArr, setMyArr] = useState([1, 2, 3, 4]) // the state on update of which we want to call some function
const someAction = () => {
let arr = [...myArr]
arr.push(5) // perform State update
setMyArr(arr) // set new state
}
useEffect(() => { // this hook will get called every time myArr has changed
// perform some action every time myArr is updated
console.log('Updated State', myArr)
}, [myArr])
When you're updating your state using a property of the current state, React documentation advise you to use the function call version of setState instead of the object.
So setState((state, props) => {...}) instead of setState(object).
The reason is that setState is more of a request for the state to change rather than an immediate change. React batches those setState calls for performance improvement.
Meaning the state property you're checking might not be stable.
This is a potential pitfall to be aware of.
For more info see documentation here: https://facebook.github.io/react/docs/react-component.html#setstate
To answer your question, i'd do this.
checkPencil(){
this.setState((prevState) => {
return {
pencil: !prevState.pencil
};
}, () => {
this.props.updateItem(this.state)
});
}
It's because it happens asynchronously, so means in that time might not get updated yet...
According to React v.16 documentation, you need to use a second form of setState() that accepts a function rather than an object:
State Updates May Be Asynchronous
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.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
First set your value. after proceed your works.
this.setState({inputvalue: e.target.value}, function () {
this._handleSubmit();
});
_handleSubmit() {
console.log(this.state.inputvalue);
//Do your action
}
I used both rossipedia's and Ben Hare's suggestions and did the following:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
}, this.updatingItem);
}
updatingItem(){
this.props.updateItem(this.state)
}
Ben has a great answer for how to solve the immediate issue, however I would also advise to avoid duplicating state
If a state is in redux, your checkbox should be reading its own state from a prop or store instead of keeping track of the check state in both its own component and the global store
Do something like this:
<p>
<input
type="checkbox"
name="area" checked={this.props.isChecked}
onChange={this.props.onChange}
/>
Writing Item
</p>
The general rule is that if you find a state being needed in multiple places, hoist it up to a common parent (not always redux) to maintain only having a single source of truth
try this
this.setState({inputvalue: e.target.value}, function () {
console.log(this.state.inputvalue);
this.showInputError(inputs[0].name);
});
showInputError function for validation if using any forms
As mentioned above setState() is asynchronous in nature. I solved this issue simply using async await.
Here's an example for refernce:
continue = async (e) => {
e.preventDefault();
const { values } = this.props;
await this.setState({
errors: {}
});
const emailValidationRegex = /^(([^<>()\[\]\.,;:\s#\"]+(\.[^<>()\[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i;
if(!emailValidationRegex.test(values.email)){
await this.setState((state) => ({
errors: {
...state.errors,
email: "enter a valid email"
}
}));
}
}
You can also update the state twice like below and make the state update immediately, this worked for me:
this.setState(
({ app_id }) => ({
app_id: 2
}), () => {
this.setState(({ app_id }) => ({
app_id: 2
}))
} )
Here is React Hooks based solution.
Since React useState updates state asynchronously, check them in the useEffect hook if you need to see these changes.
Make sure to give the initialState in the useState each time using a variable. Like line 1 and 2. If I did not give anything in it it would work on double click to fill the errors variable.
1) let errorsArray = [];
2) let [errors, setErrors] = useState(errorsArray);
3) let [firstName, setFirstName] = useState('');
4) let [lastName, setLastName] = useState('');
let [gender, setGender] = useState('');
let [email, setEmail] = useState('');
let [password, setPassword] = useState('');
const performRegister = () => {
console.log('firstName', isEmpty(firstName));
if (isEmpty(firstName)) {
console.log('first if statement');
errorsArray.push({firstName: 'First Name Cannot be empty'});
}
if (isEmpty(lastName)) {
errorsArray.push({lastName: 'Last Name Cannot be empty'});
}
if (isEmpty(gender)) {
errorsArray.push({gender: 'Gender Cannot be empty'});
}
if (isEmpty(email)) {
errorsArray.push({email: 'Email Cannot be empty'});
}
if (isEmpty(password)) {
errorsArray.push({password: 'Password Cannot be empty'});
}
console.log('outside ERRORS array :::', errorsArray);
setErrors(errorsArray);
console.log('outside ERRORS :::', errors);
if (errors.length > 0) {
console.log('ERROR exists');
}
};

React - Substitute for `setState` Callback in Functional Components?

We have migrated to 'React Functional Components' instead of 'Class based Component'. I cannot find the substitute logic for setState callback function. I.e, I have a functional component with state, and I want to create an event handler function that mutates the state multiple times in sequence, the caveat being that I dont know the current value of state (it may be true/false). The following example may make more sense.
const Example = () => {
const [ openDoor, setOpenDoor ] = useState(false);
// the following handler should swich 'openDoor' state to inverse of
// current state value. Then after setTimeout duration, inverse it again
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
// within setTimeout below, '!openDoor' does not work because it still
// receives the same value as above because of async nature of
// state updates
setTimeout(() => setOpenDoor(!openDoor), 500)
}
return(...);
}
In class based components, we had callback argument which would update state after previous update. How do I achieve the same in the above functional component using state hook?
I wonder if useEffect is the best solution. Specially when calling setTimeout within useEffect is going to cause an infinite loop since every time we call setOpenDoor, the app renders and then useEffect is called calling again a setTimeOut that will call a setOpenDoor function... Graphically:
setTimeout -> setOpenDoor -> useEffect -> setTimeout -> ... hell
Of course you could use an if statement wihin useEffect the same way that #ksav suggested but that does not accomplish one requirement of #Kayote:
I dont know the current value of state (it may be true/false)
Here is a solution that works without useEffect and accomplish the requirement stated above:
Code working in codesandbox
There, see the importance of this piece of code:
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
setTimeout(() => setOpenDoor(openDoor => !openDoor), 500);
};
Since we are using setTimeout, we need to pass callback to setOpenDoor instead of the updated state. This is because we want to send the 'current' state. If we sent the new state instead, by the time that setTimeOut processes that state, it will have changed (because we did it before setTimeOut executes its callback with setOpenDoor(!openDoor);) and no changes will be made.
You can use useEffect hook to see when the state change happend.
useEffect(() => {
// do something
console.log('openDoor change', openDoor)
}, [openDoor]);
I'll tell you that it works pretty much in the same way as this.setState, you just a pass a callback function which takes previous state as a parameter and returns new state(docs)
const Example = () => {
const [openDoor, setOpenDoor] = useState(false);
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
}
return(...);
}
In order for you know when it changes you can use useEffect callback, which's gonna be called each time something changes in the dependencies array(docs)
const Example = () => {
const [openDoor, setOpenDoor] = useState(false);
useEffect(() => {
console.log('openDoor changed!', openDoor)
}, [openDoor])
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
}
return(...);
}
:)
You can use useEffect hook to achieve this.
setOpenDoor(!openDoor);
useEffect(() => {
// Here your next setState function
}, [openDoor]);
For more information on hooks please check out https://reactjs.org/docs/hooks-effect.html
You should just using setTimeout within useEffect callback:
const App = () => {
const [openDoor, setOpenDoor] = useState(false);
const toggle = () => setOpenDoor(prevOpen => !prevOpen);
useEffect(() => {
const id = setTimeout(() => toggle(), 1000);
return () => clearTimeout(id);
}, [openDoor]);
return <Container>isOpen: {String(openDoor)}</Container>;
};
import React, { useState, useEffect } from "react";
const Example = () => {
const [openDoor, setOpenDoor] = useState(false);
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
};
useEffect(() => {
console.log(openDoor);
if (openDoor) {
setTimeout(() => setOpenDoor(!openDoor), 1500);
}
}, [openDoor]);
return (
<>
<button onClick={toggleOpenDoor}>Toggle</button>
<p>{`openDoor: ${openDoor}`}</p>
</>
);
};
export default Example;
Codesandbox

Categories

Resources