2

I am tring to update a single value in the state, this is my state:

state = {
    courses: [
      {
        id: 1,
        courseName: 'lenguage',
        courseType: false,
        courseHours: 10,
        courseGrade: ''
      },{
        id: 2,
        courseName: 'Math',
        courseType: true,
        courseHours: 20,
        courseGrade: ''
      },{
        id: 3,
        courseName: 'Biology',
        courseType: false,
        courseHours: 30,
        courseGrade: ''
      }
    ]
  };

I got the value I want to update and the index of the object inside the array but I cant find a way to update the value... This is what I have so far:

  updateCourseGrade(courseGrade, id){
        const index = this.state.courses.findIndex(course => (
          id === course.id
        ));



    const courses = [...this.state.courses];
    courses[index].courseGrade = courseGrade;
    this.setState({courses});
  }

as well I thought tried this block

const course = this.state.courses[index];
this.setState({
  courses: [...this.state.courses, course.courseGrade: courseGrade]
})

any help would be amazing tnx!

DavSev
  • 1,005
  • 4
  • 22
  • 47

4 Answers4

4

When you're working with React state, you're working with an immutable data structure Immutable.js can be a lifesaver.

But we can also do it without dependencies with map and object spread:

this.setState({
    courses: this.state.courses.map(course => course.id === id ? { ...course, courseGrade } : course)
})
  • You shouldn't use the state directly to map it either. Bad practice. – Subhendu Kundu Mar 04 '19 at 14:30
  • @SubhenduKundu it's actually correct to do it this way. The map function on an array creates a new array, therefore this is so far the only non-mutating solution, albeit a bit hard to follow for newer developers :) – ZekeDroid Mar 04 '19 at 14:32
  • 2
    @SubhenduKundu yup preferably you can also use the form of `setState` that receives a function instead of an update object, but I think it's only required for the rare occasion of possible race condition -- such as when toggling things – Rakha Kanz Kautsar Mar 04 '19 at 14:33
  • it's not "required" per se, but you are correct in that the biggest benefit of passing a function to set state is that your calls to setState will happen synchronously https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous – Robbie Milejczak Mar 04 '19 at 14:44
  • 1
    Yup you are right, however your might wanna look at https://codereview.stackexchange.com/questions/160411/deep-clone-objects also there few articles on this why you shouldn't use the state.map directly. I will update you once I get a hold of my laptop. – Subhendu Kundu Mar 04 '19 at 14:44
4

Ideally you should use this

this.setState((prevState) => {
// Use prevState and do the map just like someone suggested in one of the answer
 courses: prevState.map(course => course.id === id ? {...course, courseGrade } : course)
})
Subhendu Kundu
  • 3,618
  • 6
  • 26
  • 57
1

Generally when working with state you want to perform only pure operations, because mutating state can lead to confusing, difficult to locate bugs.

updateCourseGrade = (courseGrade, id) => {
  const course = this.state.courses.find(course => course.id === id);
  if (course) {
    const updatedCourse = { ...course, courseGrade };
    const updatedCourses = this.state.courses
      .filter(course => course.id !== id)
      .concat(updatedCourse);
    this.setState({ courses: updatedCourses });
  }
}

This can get pretty cumbersome, but it is core to the philosophy of React itself. If you wanna make it easier on yourself, I recommend checking out immer, an NPM library that exports one function, produce. I don't normally recommend third party libraries as solutions, but I particularly love immer. It will look weird at first, but it works by using something called a Proxy (a new language feature in ES6):

updateCourseGrade = (courseGrade, id) => {
  this.setState((
    produce(draftState => {
      const index = draftState.courses.findIndex(course => (
        id === course.id
      ));
      draftState.courses[index].courseGrade = courseGrade;
    })
  ))
}

Often immer is not needed, but when working with arrays of objects and especially with nested object structures it can be a godsend in helping you write clean code that works well.

Robbie Milejczak
  • 5,664
  • 3
  • 32
  • 65
0

you cant change const variables,

const courses = [...this.state.courses];
    courses[index].courseGrade = courseGrade;
    this.setState({courses});

if you have the value and the index of your data in array just do it

let state = {...this.state}

state.courses[INDEX].courseGrade = courseGrade;

this.setState({...state})

i hope it useful

Edison Junior
  • 320
  • 1
  • 6
  • Why would this work? Please explain what the issue was and why changing to this will work – Rajesh Mar 04 '19 at 14:23
  • 3
    Good start to the solution! But be careful that this is a mutating change and should be avoided if possible. – ZekeDroid Mar 04 '19 at 14:23
  • you definitely can *change* them, the only thing `const` prevents is reassignment. It's also worth nothing that the spread operation only shallow clones, so the courses array and each individual course are still passed by reference and being mutated here. It would be better to use `this.state.slice()` – Robbie Milejczak Mar 04 '19 at 15:17