cmtclown
cmtclown

Reputation: 1

react updating state in an array inside of an object which is inside of another array

I am trying to add a new task for each person separately on the task list. I successfully get the id for each person and add new task to that person info array in addItem function and I'm even able to see my new state being updated with the correct value when I log to the console. However, I'm getting this error-> Cannot read property 'map' of undefined. Also, what's the best way to update the state in these kind of cases? I know that spread operator or Object.assign is being used a lot but what should I think about when there is a nested levels of state?

class App extends React.Component {
  state = {
    data: [
      {
        id: 0,
        title: "Winnie",
        info: [
          "Buy eggs from the grocery store",
          "Return text books to the campus",
          "Buy eggs from the grocery store",
          "Return text books to the campus"
        ]
      },
      {
        id: 1,
        title: "Maggie",
        info: [
          "Finish cleaning before 11 pm",
          "See if Michael has any recommendations",
          "Buy eggs from the grocery store",
          "Return text books to the campus"
        ]
      }
  };

  addItem = id => {
    const enteredTask = prompt("Please enter a task");

    this.state.data.map(d => {
      if (d.id == id) {
        d.info = [...d.info, enteredTask];
        console.log("d info", d);
        this.setState({
          data: [...this.state.data, d.info]
        });
        console.log("state is", this.state.data);
      }
    });
  };

  render() {
    return (
      <div>
        <h3 className="header">Dashboard</h3>
        <div className="card-parent">
          {this.state.data.map(d => {
            return <Card key={d.id} info={d} addItem={this.addItem} />;
          })}
        </div>
      </div>
    );
  }
}



class Card extends React.Component {
  render() {
    return (
      <div className="card-style">
        <h4 className="card-header">{this.props.info.title}</h4>
        {this.props.info.info.map(i => {
          return <p className="card-info">{i}</p>;
        })}
        <button
          onClick={() => this.props.addItem(this.props.info.id)}
          className="card-button"
        >
          Add New Task
        </button>
      </div>
    );
  }
}

Upvotes: 0

Views: 37

Answers (3)

cmtclown
cmtclown

Reputation: 1

Here's the answer based on Knick's suggestion with a slight change, thanks everybody for helping

  addItem = id => {
    const enteredTask = prompt("Please enter a task");
    let new_arr = [...this.state.data];

    this.state.data.map(d => {
      if (d.id === id) {
        new_arr[id].info = [...d.info, enteredTask];
        this.setState({
          data: new_arr
        });
      }
    });
  };

Upvotes: 0

Steve Holgado
Steve Holgado

Reputation: 12071

You need to check for this.state.data before using map in your render method to stop the error that you are getting:

render() {
  return (
    <div>
      <h3 className="header">Dashboard</h3>
      <div className="card-parent">
        {this.state.data && this.state.data.map(d => {
          return <Card key={d.id} info={d} addItem={this.addItem} />;
        })}
      </div>
    </div>
  );
}

...and the same in the render method of your Card component:

{this.props.info.info && this.props.info.info.map(i => {
  return <p className="card-info">{i}</p>;
})}

You can then update your addItem method to call setState after updating the data rather than in each iteration of map:

addItem = id => {
  const enteredTask = prompt("Please enter a task");

  const newData = this.state.data.map(d => {
    if (d.id == id) {
      d.info = [...d.info, enteredTask];
    }
    return d;
  });

  this.setState({ data: newData });
};

I hope this helps.

Upvotes: 0

Shravan Dhar
Shravan Dhar

Reputation: 1560

This error is probably coming from: this.props.info.info.map inside your render function. I think you intended it to be this.props.info.map instead.

Ideally you can do this.props.info && this.props.info.map. Just make sure whatever array you've in your code, before mapping over it you check for it's existence first. Do this before mapping any array in your code

// for any array named arr
arr && arr.map(//your operation)

Even it arr = [], i.e. has no elements, it will still be true when it's typecasted to boolean.

Coming to the second part, the idea behind spread operator or Object.assign is to change reference, allowing react to update state.

But note, both these methods change only reference to one level down. So for nested objects or more specifically for your case, you can do the following:

  addItem = id => {
    const enteredTask = prompt("Please enter a task");
    const newData = [...this.state.data];  // changed array's reference here

    // changed reference of nested object
    newData[id].info = [...d.info, enteredTask]; 
    this.setState({ data: newData });
 };

Hope this helps :)

Upvotes: 1

Related Questions