最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Setting an array to null and then updating it in useEffect React - Stack Overflow

programmeradmin1浏览0评论

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 before setMeals([...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 of setMeals([...meals, ...resp.data]) try setMeals(resp.data) which is functionally equal to setMeals([...resp.data]). – Funk Soul Ninja Commented Jan 3, 2020 at 20:48
 |  Show 11 more ments

3 Answers 3

Reset to default 2 +200

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./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;

发布评论

评论列表(0)

  1. 暂无评论