I have a function that sets state twice, however - the second setState has to occur after 500ms since first setState has occured (animation purposes).
Code looks like:
const showAnimation = () => {
this.setState({ hidden: false });
setTimeout(() => {
this.setState({ hidden: true });
}, 500);
};
However - if I do it this way, React somehow merges these two setState's into one and my animation doesn't work as expected.
But, if I use a hack:
const showAnimation = () => {
setTimeout(() => {
this.setState({ hidden: false });
}, 0); // ------------------------------> timeout 0
setTimeout(() => {
this.setState({ hidden: true });
}, 500);
};
It works as expected. But still, I don't really like it and Im afraid that it may be some kind of a hack. Is there any better solution for such case? Thanks :)
As setState are async in React you might not get updated state immediately but setState gives you prevState param in setState function to get last updated state so you won't merge state
The syntax goes like this in your case
this.setState((prevState) => { hidden: false }, () => {
setTimeout(() => {
this.setState({ hidden: !prevState.hidden });
}, 500);
});
just update your value to the updated state using prevState
If I understand your problem correct this should work fine
Please let me know if more clarification required
If you try something like that:
const showAnimation = () => {
this.setState({ hidden: false }, () => {
setTimeout(() => {
this.setState({ hidden: true });
}, 500);
}
}
I would personally use animations within JS if you are looking to time it without setTimeout. However this may be down to the face that 'setState' is async within react.
similar :
Why is setState in reactjs Async instead of Sync?
However react does expose a callback within setState - this works for me
this.setState(
{ hidden : false },
() => {
setTimeout(()=>{this.setState({hidden : true})}, 500)
}
);
For rendering performance, react batches calls to setState such that sequential calls will be executed together and will often be reflected in the same render cycle. Per the docs:
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.
In order to ensure that the first setState has been executed prior to your second call, you can pass setState a callback as the second argument. It's not perfect, but something like the following will ensure that your second call to setState will only happen once hidden: false.
const showAnimation = () => {
this.setState({ hidden: false }, () => setTimeout(() => {
this.setState({ hidden: true });
}, 500););
};
Related
I have a local state selectedProducts holding a number representing the number of products the client has selected, while exiting the screen I want to update the number in redux so it can be viewed in the cart on a different screen.
but every time my local selectedProducts updates it stacks another function call on beforeRemove event
I know this is a noob problem but I've spent hours trying to find a solution to this problem and advice would be very helpful :)
useEffect(()=>{
navigation.addListener('beforeRemove', () => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
dispatch(addToCartMultipleQty(selectedProducts));
});
},[selectedProducts])
selectedProducts is a number state whose initial value is null and on a button click event its value is either incremented or decremented by 1, if its previous value is null then its value is set to 1
Note: I just want to update selectedProducts state's latest value only once in redux when the screen is about to be exited/unmonted
You can try this:
useEffect(()=>{
navigation.addListener('beforeRemove', () => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
dispatch(addToCartMultipleQty(selectedProducts));
});
return () => {
navigation.removeListener('beforeRemove');
}
}, [selectedProducts])
Add that in return at the end of useEffect it will work as componentWillUnmount in functional component
useEffect(() => {
return () => {
// Anything in here is fired on component unmount.
}
}, [])
Edit: In Your case
useEffect(() => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
return () => {
// Anything in here is fired on component unmount.
if (selectedProducts) {
dispatch(addToCartMultipleQty(selectedProducts));
}
};
}, [selectedProducts]);
The react setState after action doesn't work.
handleChange = () => {
this.setState({foo: 'bar'}); < - it working
console.log('hellow') < - does not working, console is clean
}
As far i checked my state, I did everything right about the state values.
I don't know what is the problem
---- update----
The project that created a new create-react-app operates very normally.
I don't know why the above issue occurred, and it doesn't make sense in common sense, but it seems that it's because the project is so messed up.
Thank you for answering such limited situations.
Your code is correct but you need to call handleChange() method
Ex :-
componentDidMount() {
this.handleChange();
}
handleChange = () => {
this.setState({ foo: "bar" });
console.log("Hello");
};
Please try this
handleChange = () => {
this.setState({ foo: "bar" }, () => {
console.log("hellow");
});
};
You can call callback in setState as below
handleChange = () => {
this.setState({foo: 'bar'}, () => {
console.log('hellow')
});
}
I'm trying to set a state of the user by getting a value from my database and then using it. For some reason the state does not update itself I have tried await and async. What other options exists if this one can't be reliable to make this be a value.
I do get the following error : Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
- node_modules/fbjs/lib/warning.js:33:20 in printWarning
- node_modules/fbjs/lib/warning.js:57:25 in warning
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:12196:6 in warnAboutUpdateOnUnmounted
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:13273:41 in scheduleWorkImpl
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:6224:19 in enqueueSetState
- node_modules/react/cjs/react.development.js:242:31 in setState
* null:null in componentWillMount$
- node_modules/regenerator-runtime/runtime.js:62:44 in tryCatch
- node_modules/regenerator-runtime/runtime.js:296:30 in invoke
- ... 13 more stack frames from framework internals
constructor() {
super();
this.state = {
userPhoneNumber: "",
};
}
async componentWillMount() {
await firebase.database().ref('/Users/' + firebase.auth().currentUser.uid).on('value', async snap => {
if (snap) {
await this._setPhone(snap.val())
} else {
this.props.navigation.navigate('Phone')
}
});
console.log(this.state.userPhoneNumber);
}
_setPhone = (snap) => {
const val = parseInt(snap, 10);
this.setState({
userPhoneNumber: val
})
};
If you are sure that you are receiving the correct value for snap. Then the issue that you have is that setState is asynchronous. That means it takes time for state to set.
Unfortunately they way you are checking your state to see if the value has been set is wrong.
You should use a callback in the setState function, so your setState would become:
this.setState({userPhoneNumber: val}. () => console.log(this.state.userPhoneNumber));
I would recommend taking a read of the following articles by Michael Chan that go into more detail about setting state
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
There are also a few issues with your use of async/await and promises it looks like you are mixing the syntax between them. You either use one or the other, not both. This article goes into detail about the differences between them.
this.setState does not return a promise so using await this.setState does nothing.
This is how I would refactor your code:
componentDidMount() { // componentWillMount is deprecated
// you are using a promise to access firebase so you shouldn't be using `async/await`
firebase.database().ref('/Users/' + firebase.auth().currentUser.uid).on('value', snap => {
if (snap) {
this._setPhone(snap.val()) // remove the await as it is not doing anything
} else {
this.props.navigation.navigate('Phone')
}
});
}
_setPhone = (snap) => {
const val = parseInt(snap, 10);
this.setState({ userPhoneNumber: val}, () => console.log(this.state.userPhoneNumber)) // include the callback to check the value of state
};
Updated question
You must be calling setState when the component has been unmounted. You need to check to make sure that your component is mounted before calling setState.
One way of doing it is by having a boolean flag that monitors when the component is mounted.
componentDidMount () {
this._isMounted = true;
}
componentWillMount () {
this._isMounted = false;
}
when you set your state you can do something like this
if (this._isMounted) {
this.setState({key: value});
}
You can see more about it here https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
Just set a _isMounted property to true in componentDidMount and set
it to false in componentWillUnmount, and use this variable to check
your component’s status
This it not an ideal solution but it is the simplest, as you really should be cancelling your promises etc, so that setState is never called as the promise has been cancelled.
Can I explicitly call this.setState() in the definition of the callback passed to this.setState()?
this.setState(
{
openA:true
},
() => {
this.setState({
openB: false
})
}
)
This can be done, and this will result in 2 re-renders instead of 1. Usually there is no need to do this.
If setState are independent, it can be:
this.setState({ openA:true });
this.setState({ openB:false });
If updated states depend on each other, updater function should be used:
this.setState({ openA:true });
this.setState(state => ({ openB: !state.openA }));
Yes. It will run it after the first setState is complete.
This is how my state looks like:
constructor(props, context) {
super(props, context);
this.state = {
show: false,
btnLabel: 'GO!',
car: {
owner: false,
manufacturer: false,
color: false
}
};
}
and this is how I modify state:
handleClickFetchPrice() {
this.setState({btnLabel: 'Fetching data...' });
console.log(this.state.fetchPriceBtn);
const url = 'some url';
axios.get(url)
.then(res => {
let car = [...this.state.car];
car.owner = res.data.owner;
car.manufacturer = res.data.manufacturer;
car.color = res.data.color;
this.setState({car});
})
}
The attribute car is updated, but fetchPriceBtn is not - the output of console.log(this.state.fetchPriceBtn); is still GO!.
What am I overlooking? Why the fetchPriceBtn is not updated?
React setState is an asynchronous process - you don't know exactly when it will be updated, you can only schedule the update.
To achieve your desired functionality, you can provide a callback into the setState method.
this.setState({ btnLabel: 'Fetching data...' }, () => console.log(this.state.fetchPriceBtn))
You can learn more following the documentation on the method.
#christopher is right, setState is an asynchronous process. But when second time call handleClickFetchPrice() function your btnLabel is value will be equal to Fetching data...
As answered in previous answers setState is asynchronous, so your console.log can't catch up the state change immediately. Again as suggested you can use callback function to track this change but if you use console.log just for debugging or want to see what changes in your state you can do this in your render function. And using a callback just for debug is not a nice way. Its purpose somehow different and if you check the official documentation, componentDidMount method is being suggested for such logic.
render() {
console.log( this.state.foo );
return (...)
}
If you do that you see two console.log output, one before state change and one after.
Also, your state operations might be enhanced. You car property is not an array, but you are converting it to an array and setting it? Is this what you intend:
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color } } ) );
})
Here we are not mutating our state directly, instead we are using spread operator and setting the desired properties. For your example we are setting the whole property actually.
One last note, I think you want to do that something like that:
this.setState( { btnLabel: "fetching } );
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color }, btnLabel: "go" } ) );
})
If your intention is somehow to do a status change/check this might no be a good logic as you have seen setState is not synchronous. Do this carefully.