this.setState()
gets called asynchronously so you cannot rely on this.state
referencing the updated value immediately after calling this.setState()
. Have a read through the FAQ on component state.
If you want to reference the updated value of newPrice
after the state has been updated, you can:
- Use the
componentDidUpdate()
lifecycle method. See https://reactjs.org/docs/react-component.html#componentdidupdate.
addIngredientHandler = (type) => {
let oldCount = this.state.ingredients[type];
let copyState = { ...this.state.ingredients };
let newPrice = 0;
copyState[type] = oldCount + 1;
this.setState((prevState) => {
newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
newPrice = Math.round(newPrice * 100) / 100;
return { ingredients: copyState, totalPrice: newPrice }
});
}
componentDidUpdate(prevProps, prevState) {
if (prevState.totalPrice !== this.state.totalPrice) {
this.updatePurchaseable(this.state.ingredients, this.state.totalPrice);
}
}
- Use the 2nd argument to
this.setState()
. See the docs at https://reactjs.org/docs/react-component.html#setstate.
addIngredientHandler = (type) => {
let oldCount = this.state.ingredients[type];
let copyState = { ...this.state.ingredients };
let newPrice = 0;
copyState[type] = oldCount + 1;
this.setState((prevState) => {
newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
newPrice = Math.round(newPrice * 100) / 100;
return { ingredients: copyState, totalPrice: newPrice }
}, () => {
this.updatePurchaseable(this.state.ingredients, this.state.totalPrice);
});
}
- Use
ReactDOM.flushSync()
. See https://github.com/reactwg/react-18/discussions/21.
import { flushSync } from 'react-dom';
addIngredientHandler = (type) => {
let oldCount = this.state.ingredients[type];
let copyState = { ...this.state.ingredients };
let newPrice = 0;
copyState[type] = oldCount + 1;
flushSync(() => {
this.setState((prevState) => {
newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
newPrice = Math.round(newPrice * 100) / 100;
return { ingredients: copyState, totalPrice: newPrice }
});
});
this.updatePurchaseable(copyState, newPrice);
}
If I were to write this method, I would recommend using the componentDidUpdate
lifecycle method as this will ensure updatePurchaseable
is always called when the total price changes. If you only call updatePurchaseable
inside your event handler, then you may end up with a bug if the price changes outside of that handler.
addIngredientHandler = (type) => {
this.setState(prevState => {
let totalPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
totalPrice = Math.round(totalPrice * 100) / 100;
return {
ingredients: {
...prevState.ingredients,
[type]: prevState.ingredients[type] + 1,
},
totalPrice,
};
});
}
componentDidUpdate(prevProps, prevState) {
const { totalPrice, ingredients } = this.state;
if (prevState.totalPrice === totalPrice) {
/*
Bail early. This is a personal code style preference. It may
make things easier to read as it keeps the main logic on the
"main line" (un-nested / unindented)
*/
return;
}
/*
If `updatePurchaseable` is a class method then you don't need to
pass state to it as it will already have access to `this.state`.
If `updatePurchaseable` contains complicated business logic,
consider pulling it out into its own module to make it easier
to test.
*/
this.updatePurchaseable(ingredients, totalPrice);
}