Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting an array to null and then updating it in useEffect React

I'm new to React. I've a MealList component 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.

like image 994
Melissa Stewart Avatar asked Jan 01 '20 19:01

Melissa Stewart


2 Answers

If 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]
like image 118
6502 Avatar answered Oct 21 '22 09:10

6502


Here is an example (trying to mimic your code):

Stackblitz demo: https://stackblitz.com/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 component 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>
  );
};
like image 43
Eugen Sunic Avatar answered Oct 21 '22 09:10

Eugen Sunic