0

The below is a dummy reproduction of a part of an app I am writing.

I am mapping an array of data using const test that return days of the week. Once I click on a certain day, and this certain day is not present in this.state.testingDates.array i use setState to add this day of the week into this.state.testingDates.array.

When I click on a certain day I get the following error Invalid attempt to spread non-iterable instance.

Why can't I spread ...array, so I preserve the old state with my state organized in this way, having array: [] at the same level than classFrequency?

If I move array one level up(ending with this.state.array, it all works smoothly.

Here is my code

import React from "react";
import "./styles.css";

export default class App extends React.Component {
  state = {
    testingDates: {
      array: [],
      classFrequency: [
        { day: "Sun", DayOfWeek: 0, toggleClassColor: "inherit" },
        { day: "Mon", DayOfWeek: 1, toggleClassColor: "inherit" },
        { day: "Tue", DayOfWeek: 2, toggleClassColor: "inherit" },
        { day: "Wed", DayOfWeek: 3, toggleClassColor: "inherit" },
        { day: "Thu", DayOfWeek: 4, toggleClassColor: "inherit" },
        { day: "Fri", DayOfWeek: 5, toggleClassColor: "inherit" },
        { day: "Sat", DayOfWeek: 6, toggleClassColor: "inherit" }
      ]
    }
  };

  nonIterableHandler = (childDay, childIndex) => {
    const classFrequency = this.state.testingDates.classFrequency;
    if (!this.state.testingDates.array.includes(childDay.day)) {
      this.setState(
        ({ array }) => ({
          array: [...array, classFrequency[childIndex].day]
        }),
        () => {
          console.log("array spreads...", this.state.array);
        }
      );
    }
  };

  render() {
    let test = this.state.testingDates.classFrequency.map((val, index) => (
      <p
        key={val.day}
        style={{ cursor: "pointer" }}
        onClick={() => this.nonIterableHandler(val, index)}
      >
        {val.day}
      </p>
    ));

    return (
      <div className="App">
        <h1>testing non-iterable</h1>
        {test}
      </div>
    );
  }
}
Arp
  • 979
  • 2
  • 13
  • 30
  • Just linking this here: [How to update nested state properties in React](https://stackoverflow.com/questions/43040721/how-to-update-nested-state-properties-in-react) – Emile Bergeron Mar 11 '20 at 23:42

1 Answers1

2

Your initial state does not have an array property at the top level:

  state = {
    testingDates: {
      array: [],
      // ...
    }
  }

The array is inside the testingDates object. So if you do

this.setState(
  ({ array }) =>

it won't work, because no array property exists at the top.

You can properly extract the array using nested destructuring if you want:

this.setState(
  ({ testingDates: { array }}) =>

And make sure to assign the new array to the proper nested property:

this.setState(
  ({ testingDates: { array, ...restTesting }, ...restTop }) => ({
    testingDates: {
      array: [...array, classFrequency[childIndex].day],
      ...restTesting
    },
    ...restTop
  }),
  () => {
    console.log("array spreads...", this.state.testingDates.array);
  }
);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Oh I see! You mean top by saying that there is no ```object``` called ```array``` at the top level of ```state```? Interesting... I am still picking the syntax – Arp Mar 11 '20 at 23:03
  • alright, and what If ```testingDates``` is one level deeper into ```state```? – Arp Mar 11 '20 at 23:12
  • You could do the same sort of thing: `({ testingDates: { nested: { array, ...restNested } ...restTesting }, ...restTop })` Or, at that point, it'd probably be better to use only a single level of destructuring and list the properties that need to be spread via dot notation – CertainPerformance Mar 11 '20 at 23:15
  • oh I see. So lets say that ```testingDates``` is now inside ```newClassForm```, making ```this.state.newClassForm.testingDates.array```. I see what you say. So I guess this would be a clean solution, wright? ```this.setState( ({ newClassForm: { testingDates } }) => ({ array: [...testingDates.array, classFrequency[childIndex].day] }), () => { console.log("array spreads...", this.state.array); } );``` – Arp Mar 11 '20 at 23:17
  • The object returned from the function should have the same structure as in the initial state, so it needs a `newClassForm` object. `({ ..restTop, newClassForm: ({ ...restClassForm, array: [...testingDates.array, classFrequency[childIndex].day] } })` Make sure to spread the rest of the properties in the state as well, else they'll be lost – CertainPerformance Mar 11 '20 at 23:23
  • Thanks for shedding some light. I did not manage to figure out our last comment's syntax.. This is what I came with: (next comment) – Arp Mar 12 '20 at 14:36
  • this.setState( ({ newClassForm: { testingDates: { array, ...restTesting }, ...restClassForm }, ...restTop }) => ({ newClassForm: { testingDates: { array: [...array, classFrequency[childIndex].day], ...restTesting }, ...restClassForm }, ...restTop }), () => { console.log( "array spreads", this.state.newClassForm.testingDates.array ); } ); – Arp Mar 12 '20 at 14:36
  • on your last comment It seemed that you managed to keep the state structure shorter once you use ```setState``` and I did not manage to use ```...restTop``` at the beginning. How would it be possible to shorten my solution above into a slicker syntax? If so, could you please share with me the whole deal? – Arp Mar 12 '20 at 14:39