I am relatively new to the JavaScript world, i am learning react and have encountered a weird issue see this code
addIngredientHandler = (type) => {
let oldCount = this.state.ingredients[type];
let copyState = {...this.state.ingredients};
let newPrice = 0;
copyState[type] = oldCount + 1;
this.setState( (prevState, prevProps) => {
newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
newPrice = Math.round(newPrice * 100) / 100;
console.log('newprice inside setState: ' + newPrice);
return { ingredients: copyState, totalPrice: newPrice}
} );
console.log('newprice outside setState: ' + newPrice);
this.updatePurchaseable(copyState, newPrice);
}
here i am concerned with the newPrice variable which is used update the state when more items are added, which works fine
problem is after the this.setState
return the newPrice gets retested to 0 again
so i can't use it for the function at the bottom.
Yes i can use the state variable directly but due to the asnyc nature of setState
execution i wanted to pass the variable value instead.
in the console you can see that first the outer console log gets executed then the inside one due to async nature of setState
maybe i am not getting some lifecycle react has that is generating this type of behavior.
here is the state values, in the values shouldn't matter but still for a better picture
state = {
ingredients: {
salad: 0,
bacon: 0,
meat: 0,
cheese: 0,
},
purchasable: false,
totalPrice: 0
}
Any hint helps, thanks for reading.
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:
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);
}
}
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);
});
}
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);
}