spread operator not working in nested setState within useEffect using .map() function

Viewed 358
  const [columns, setColumns] = useState({
    Configured: {
      progress: "Configured",
      tasks: [],
    },
    Approved: {
      progress: "Approved",
      tasks: [],
    },
    AwaitingPO: {
      progress: "Awaiting PO",
      tasks: [],
    },
    InProduction: {
      progress: "In-Production",
      tasks: [],
    },
    Delivered: {
      progress: "Delivered",
      tasks: [],
    },
  });

  const { loading, error, data } = useQuery(GET_CUSTOMERS);

  useEffect(() => {
    if (data) {
      data.customer.map((item) => {
        setColumns({
          ...columns,
          [item.progress]: {
            ...columns[item.progress],
            tasks: [
              ...columns[item.progress].tasks, ///Spread operator not working here
              item
            ],
          },
        });
      });
    }
  }, [data]);

I am trying to add several objects named item to my tasks array. Out of the three items I have, only one is being added. Nested states seems to be quite hard, and I feel like I'm doing something wrong regarding useEffect or the map function itself.

2 Answers

Your effect has a missing dependency ofcolumns making columns a stale closure.

To prevent the dependency you can pass a callback to the state setter function. You are also using Array.prototype.map in a wrong way, you should be using Array.prototype.reduce:

useEffect(() => {
  setColumns((columns) =>
    data
      ? data.customer.reduce(
          (columns, item) => ({
            ...columns,
            [item.progress]: {
              ...columns[item.progress],
              tasks: [
                ...columns[item.progress].tasks,
                item,
              ],
            },
          }),
          columns
        )
      : columns
  );
}, [data]);

Because data is a useEffect object can not keep track of its internal changes because the comparison is by reference

you can do such

This is how you compare by the length of the object

React.useEffect(()=>{},[data.length])

Or alternatively, you can do so

React.useEffect(()=>{},[JSON.stringify(data)]

This way you turn it into a string and do not compare according to the reference

Related