-1

I want to increment the object inside the array students of array subjects. And the state is as given below :

  state = {
   students: [{
    name: "",
    address: "",
    class: "",
    subjects: [{ subjectName: "", duration: "" }],
  },
 ],
};

I write the Handle increment for subjects as below:

handleIncrement() {
this.state.students.map((student) => {
  this.setState({
    student: [...student.subjects, { subjectName: "", duration: "" }],
  });
});
console.log(this.state);
}

Button On Click to Increment as below:

<button
  type="button"
  className="btn btn-primary"
  style={myStyle.btnCircle}
  onClick={this.handleIncrement}>
      <i className="fa fa-plus"></i>
   </button>

Problem is I cannot increment the Object inside the subjects array. Please Can anyone help me to solve my problem on the above state format...Please do not suggest me to change my state, Data format. I'm using React js..

  • 1
    is it a typo ? `this.setState({student:`it should be `students`, you need to recreate the students array and then use `setState` – Abishek Kumar Dec 09 '20 at 16:11
  • I don’t think you need the whole map block – Faraz Dec 09 '20 at 16:13
  • 1
    @FarazShaikh I think he is trying to update every student from inside the map – Abishek Kumar Dec 09 '20 at 16:15
  • students.map will return an updated array of students which you should then use to update the entire students state – Greg M Dec 09 '20 at 16:15
  • 2
    The phrase "increment the object" is meaningless. Do you mean "add an object to the array"? – Heretic Monkey Dec 09 '20 at 16:16
  • @AbishekKumar won’t it be better to rebuild the students array then call setState once? Won’t using map re-render the whole component multiple times? – Faraz Dec 09 '20 at 16:17
  • @FarazShaikh since react has it's diffing algo in place it merges state update and so we have the callback function to `this.setState()`, but yes it's not the optimised way to do it – Abishek Kumar Dec 09 '20 at 16:20
  • @FarazShaikh Yes, that is what I was suggesting. Move the set State outside of the map so it doesn't cause a re-render multiple time. – Greg M Dec 09 '20 at 16:20
  • Well he is just trying to add one object to the object array, that can just be done by using the spread operator like many other answers have already described. Also pardon me if I’m wrong, I’m still fairly new to react’s workings – Faraz Dec 09 '20 at 16:22
  • thats right we will use spread operator in the subjects array, and no issue everyone is new at something – Abishek Kumar Dec 09 '20 at 16:23

3 Answers3

2

Typo aside, I think you need to set the result of your map back to state, or you're going to be overwriting the state multiple times.

handleIncrement() {
  this.setState({
    students: this.state.students.map((student) => ({
      ...student,
      subjects: [...student.subjects, { subjectName: "", duration: "" }]
    }))
  });
  console.log(this.state);
}

Note this will add a new entry to subjects for every student in the array, if you need it to be conditional you just need to change the map to only perform the change for the specific student, e.g.:

handleIncrement() {
  this.setState({
    students: this.state.students.map((student) => {
      if (student.name !== <name to update>) {
        return student;
      }
      return {
        ...student,
        subjects: [...student.subjects, { subjectName: "", duration: "" }]
      };
    })
  });
  console.log(this.state);
}
Matt
  • 1,073
  • 1
  • 8
  • 14
  • Thanks a lot for saving my butt...I'm very happy and so much pleasure for your answer, I'll be hoping for your solution if any problem occurs...Again Thanks a Lot... – karma dorje tamang Dec 09 '20 at 16:56
0
handleIncrement() {
   const newSubject = { subjectName: "", duration: "" }
   const newStudents = this.state.students.map((student) => 
                    { ... student, subjects: student.subjects.concat(newSubject) })
   this.setState({ students: newStudents })
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
Lawson
  • 36
  • 3
-1

I guess what you mean by increment is you want to push object(e.g { subjectName: "2", duration: "2" }) into subjects array

It is not good to call setState() in map() function. You need to create new state without mutating previous and assign it at once

let newStudents = state.students.slice(0)
newStudents = newStudents.map(student => {
  student.subjects = [...student.subjects, { subjectName: "2", duration: "2" }]
  return student
})

Now you have desired students state in newStudents and can call setState()

this.setState({students: newStudents})
Ilyas
  • 94
  • 4
  • `student.subjects = ` this is mutating the current state. – Emile Bergeron Dec 09 '20 at 16:32
  • @EmileBergeron, it is not mutating current state. It mutates "newStudents" array property. "newStudents" array is just a copy of current state, not reference. Refer to Array.slice(0) documentation – Ilyas Dec 09 '20 at 16:40
  • `newStudents` is just a shallow copy, containing all the original student objects. – Emile Bergeron Dec 09 '20 at 16:41
  • @EmileBergeron, tested you are right, sorry. This is the right way ```let newStudents = JSON.parse(JSON.stringify(state)).students``` – Ilyas Dec 09 '20 at 16:53
  • The JSON trick is a hack with a lot of downsides (performance, data loss, etc), I've always been able to easily avoid it for these reasons. The solution is already clear in [Matt's answer](https://stackoverflow.com/a/65220645/1218980). – Emile Bergeron Dec 09 '20 at 16:56
  • 1
    (the downsides of using JSON to deep clone data are highlighted in [this answer](https://stackoverflow.com/a/122704/1218980)). – Emile Bergeron Dec 09 '20 at 17:04