简体   繁体   中英

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.

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]

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>
  );
};

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 component 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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM