Reputation: 63
I'm trying to have a food filter page whereby the user presses buttons to indicate their preference so that on confirm, I would be able to have the user's preference stored as on object with keys "area", "cuisine" and "price"
. After trying various combinations, the logged object I get keeps only reflecting a change in the first update which is area. {"area": "South", "cuisine": Array [], "price": -Infinity }
instead of {"area":"South", "cuisine": "Japanese Cuisine", "price": 3}
. How do I deal with this async nature of setstate
to get all the updates correct? (the handle press functions are passed to children component for area cuisine and price)
Filter.js
const [filters, setFilters] = React.useState({});
React.useEffect(() => {
setFilters({ area: "", cuisine: "", price: 0 });
}, []);
const handleAreaPress = (area) => {
setFilters((prevState) => ({ ...prevState, area: area }));
};
const handleCuisinePress = (cuisine) => {
setFilters((prevState) => ({ ...prevState, cuisine: cuisine }));
};
const handlePricePress = (price) => {
let max = Math.max(...price);
setFilters((prevState) => ({ ...prevState, price: max }));
};
const handleConfirmPress = () => {
console.log(filters); // expected log to be {"area":"South", "cuisine": "Japanese Cuisine", "price": 3}
};
Area.js
const AreaSelection = ({ handleAreaPress }) => {
const [selected, setSelected] = React.useState([]);
const areas = ["North", "South", "East", "West", "Central"];
const handlePress = (area) => {
setSelected(area);
};
const buttons = () =>
areas.map((items) => (
<TouchableOpacity
key={items}
onPress={() => {
handlePress(items);
handleAreaPress(items);
}}
style={[
styles.button,
{
backgroundColor: selected.includes(items)
? "silver"
: "white",
},
]}
>
Cuisine.js
const CuisineSelection = ({ handleCuisinePress }) => {
const [selected, setSelected] = React.useState([]);
const cuisine = [
"Asian",
"Western",
"Chinese",
"Korean",
"Indian",
"Japanese",
"Cafe",
"Local",
];
const handlePress = (cuisine) => {
selected.includes(cuisine)
? setSelected(selected.filter((s) => s !== cuisine))
: setSelected([...selected, cuisine]);
};
const buttons = () =>
cuisine.map((items) => (
<TouchableOpacity
key={items}
onPress={() => {
handlePress(items);
handleCuisinePress(selected);
}}
style={[
styles.button,
{
backgroundColor: selected.includes(items)
? "silver"
: "white",
},
]}
>
Upvotes: 0
Views: 498
Reputation: 101
const handlePress = (cuisine) => {
let newSelected = [];
if (selected.includes(cuisine)) {
newSelected = selected.filter((s) => s !== cuisine);
} else {
newSelected = selected.concat(cuisine);
}
setSelected(cuisine);
handleCuisinePress(newSelected);
};
The selected
state you pass to handleCuisinePress
is not up to date, you shouldn't rely on it, just pass the new state you calculated in handlePress
to handleCuisinePress
Upvotes: 1
Reputation: 10873
The area code is actually working correct. The issue is in this code where you call two functions setting state:
handlePress(items);
handleCuisinePress(selected);
handlePress
sets the internal component state, and then handleCuisinePress
reads that state and sets it on the filters. However, since setting state is async when handleCuisinePress
is called, it uses the old values of state since the handlePress
results aren't applied yet.
The best way to handle this wold be to get rid of internal component state and read it directly from the filters:
// Declare array outside of component to not create it on every render
const cuisine = [
"Asian",
"Western",
"Chinese",
"Korean",
"Indian",
"Japanese",
"Cafe",
"Local",
];
const CuisineSelection = ({ handleCuisinePress, cuisine }) => {
const buttons = () =>
cuisine.map((items) => (
<TouchableOpacity
key={items}
onPress={() => {
handleCuisinePress(items);
}}
>
Upvotes: 1
Reputation: 503
You can define an expected value range and conditionally fallback to a default value.
const handlePricePress = (price) => {
const max = price >= 0 ? Math.max(...price) : 0;
setFilters((prevState) => ({ ...prevState, price: max }));
};
Upvotes: 0