Reputation: 1
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
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
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
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