I'm new to React. I've a MealList ponent to which I'm passing a set of props, based on which it make a data call and updates an array of meals, which I display in a table.
const MealList = (props) => {
const [meals, setMeals] = useState([]);
useEffect(() => {
const fetchMeals = async (userId, fromDate, toDate, fromTime, toTime) => {
...
return resp;
};
fetchMeals(localStorage.getItem('user_id'), props.fromDate, props.toDate, props.fromTime, props.toTime).then(r => {
setMeals([...meals, ...r.data])//The fetched data is stored in an array here.
});
}, [props]);
console.log(props.fromDate);
return (
<div style={{width: '70%'}}>
...
<Table striped bordered hover>
<thead>
<tr>
...
</tr>
</thead>
<tbody>
{meals.map((meal, index) => (<Meal key={meal.id} count={index +1} meal={meal}/>))}//And displayed here
</tbody>
</Table>
</div>
)
};
The problem I'm facing is that using the spread syntax setMeals([...meals, ...r.data])
appends to the existing list everytime MealList is updated via the props.
My question is how can I set the meals array back to null and then update only the new values? I've tried this:
fetchMeals(localStorage.getItem('user_id'), props.fromDate, props.toDate, props.fromTime, props.toTime).then(r => {
setMeals([]);
setMeals([...meals, ...r.data])
});
But this doesn't work either.
I'm new to React. I've a MealList ponent to which I'm passing a set of props, based on which it make a data call and updates an array of meals, which I display in a table.
const MealList = (props) => {
const [meals, setMeals] = useState([]);
useEffect(() => {
const fetchMeals = async (userId, fromDate, toDate, fromTime, toTime) => {
...
return resp;
};
fetchMeals(localStorage.getItem('user_id'), props.fromDate, props.toDate, props.fromTime, props.toTime).then(r => {
setMeals([...meals, ...r.data])//The fetched data is stored in an array here.
});
}, [props]);
console.log(props.fromDate);
return (
<div style={{width: '70%'}}>
...
<Table striped bordered hover>
<thead>
<tr>
...
</tr>
</thead>
<tbody>
{meals.map((meal, index) => (<Meal key={meal.id} count={index +1} meal={meal}/>))}//And displayed here
</tbody>
</Table>
</div>
)
};
The problem I'm facing is that using the spread syntax setMeals([...meals, ...r.data])
appends to the existing list everytime MealList is updated via the props.
My question is how can I set the meals array back to null and then update only the new values? I've tried this:
fetchMeals(localStorage.getItem('user_id'), props.fromDate, props.toDate, props.fromTime, props.toTime).then(r => {
setMeals([]);
setMeals([...meals, ...r.data])
});
But this doesn't work either.
Share Improve this question edited Jan 6, 2020 at 0:00 halfer 20.4k19 gold badges109 silver badges202 bronze badges asked Jan 1, 2020 at 19:43 Melissa StewartMelissa Stewart 3,63514 gold badges52 silver badges91 bronze badges 16- 2 can't you try setMeals(r.data) ? – pramod singh Commented Jan 1, 2020 at 19:49
- 1 according to me it should re-render after setMeals(r.data) is called whether it is async or sync. – pramod singh Commented Jan 1, 2020 at 19:58
-
1
have you tried
setMeals([...r.data])
? – EugenSunic Commented Jan 3, 2020 at 20:06 -
3
I believe is this less about React and more about understanding modern javascript. There is no point of calling
setMeals([])
right beforesetMeals([...meals, ...r.data])
. Just call it once with the data you want set into that state object. – Funk Soul Ninja Commented Jan 3, 2020 at 20:34 -
1
In your question, you say that the new items are being appended onto the array with the old items. Is the
meals
state object the old objects? If so, why are you adding it back into state along with the response? If you want to update the meal state with just the response from an api, instead ofsetMeals([...meals, ...resp.data])
trysetMeals(resp.data)
which is functionally equal tosetMeals([...resp.data])
. – Funk Soul Ninja Commented Jan 3, 2020 at 20:48
3 Answers
Reset to default 2 +200If you want the same effect of the splicing you should use
setMeals(r.data.slice());
as otherwise the reference to r.data
is passed and it can make a difference if that object is mutated after the setMeals
call.
When you pass an array to a function in Javascript the function doesn't receive a copy of the array, but a reference to the original object you passed. For example:
let x = [1,2,3], y = null;
function foo(xx) {
y = xx;
}
foo(x);
console.log(y); // gives [1, 2, 3]
x[1] = 99;
console.log(y); // gives [1, 99, 3]
Apparently the code in fetchMeals
(that we don't see) reuses the r.data
array and this creates the problem if you don't make a copy. I would probably classify this as a (design) bug in fetchMeals
as given the interface I'd expect to get a fresh answer and not a reused one that I must copy.
Note also that
x.slice()
is the same as
[...x]
Here is an example (trying to mimic your code):
Stackblitz demo: https://stackblitz./edit/react-hooks-usestate-svnmpn?file=MealList.js
The problem is with data mutation, you have to remain immutable if you want new changes to re-render you ponent and apply the update.
const MealList = props => {
const [meals, setMeals] = useState([]);
useEffect(() => {
const fetchMeals = async (userId, fromDate, toDate, fromTime, toTime) => {
return await new Promise(res =>
setTimeout(
_ =>
res({
data: [
{ id: 1, name: "sphagetti" },
{ id: 2, name: "salad" },
{ id: 3, name: "soup" },
{ id: 4, name: "bacon and eggs" }
]
}),
2000
)
);
};
fetchMeals(1, "date1", "date2", "time", "time2").then(r =>
setMeals([...r.data])
);
}, [props]);
return (
<div style={{ width: "70%" }}>
{!meals.length && <p>wait 2 seconds...</p>}
{meals.map((meal, index) => (
<div key={meal.id} count={index + 1} meal={meal}>
{meal.id + ". " + meal.name}
</div>
))}
</div>
);
};
Can you please try this approach? I believe reducer fits into your need better than simple state changes. Keep in mind that this is simple example. You can move props into the action and do the fetching in another file to keep your ponent clean and separate concerns.
You can also run code snippet here: https://codesandbox.io/s/fragrant-frog-y2e6j?fontsize=14&hidenavigation=1&theme=dark
import React, { useEffect, useReducer } from "react";
const mealsReducer = ({ meals }, action) => {
switch (action.type) {
case "ADD_MEALS": {
return {
meals: action.meals
};
}
// no default
}
};
const MealList = ({ fromDate, toDate, fromTime, toTime }) => {
const [state, dispatch] = useReducer(mealsReducer, { meals: [] });
useEffect(() => {
const fetchMeals = (userId, fromDate, toDate, fromTime, toTime) => {
return Promise.resolve({
data: [{ id: Math.floor(Math.random() * 100), name: "blabla" }]
});
};
fetchMeals(0, fromDate, toDate, fromTime, toTime).then(({ data }) =>
dispatch({ type: "ADD_MEALS", meals: data })
);
}, [fromDate, toDate, fromTime, toTime]);
return (
<ul>
{state.meals.map((meal, index) => (
<li key={meal.id}>{ meal.name }</li>
))}
</ul>
);
};
export default MealList;