2

I have a list of students and I display them on the table. There are two buttons that indicate by which value should I sort the list (name or birthdate). When the button is clicked and I sort the list, the list itself is getting sorted, but it's not updating if I don't assign the list to the new list using Object.assign(newList, oldList) and then update it by passing to the update state function updateList(newList). Here's my code:

const students= [
  {
    name: "John Done",
    year: 1,
    birthdate: "2020-01-24",
  },
  {
    name: "Another Done",
    year: 3,
    birthdate: "2002-02-20",
  },
  {
    name: "Jack London",
    year: 2,
    birthdate: "1800-01-04",
  },
  {
    name: "Text Name",
    year: 3,
    birthdate: "1990-02-24",
  },
  {
    name: "Name",
    year: 2,
    birthdate: "2005-04-01",
  },
];

ReactDOM.render(<App students={students} />, document.getElementById('root'));

function App({ students }) {
  const [studentsList, setStudentsList] = useState(students);

  const sortByYear = () => {
    // let sortedstudents = [];
    // Object.assign(sortedStudents, studentsList);
    // sorteStudents.sort((a, b) => b.year - a.year);
    // console.log(sorteStudents);
    // setStudentsList(sortedStudents);
    studentsList.sort((a,b) => b.year - a.year));
    setStudentsList(studentsList);
  };

const sortByDates = () => {
  // let sortedStudents = [];
  // Object.assign(sortedStudents, studentsList);
  // sortedStudents.sort((a, b) => new Date(b.birthdate) - new Date(a.birthdate));
  // console.log(sortedStudents);
  // setStudentsList(sortedStudents);
  studentsList.sort((a, b) => new Date(b.birthdate) - new Date(a.birthdate));
  setStudentsList(studentsList);
};

return (
<div className="App">
  <div>
    <label>
      Sort By
    </label>
    <button
      onClick={() => sortByYear()}
    >
      Year
    </button>
    <button
      onClick={() => sortByDates()}
    >
      Most Old
    </button>
  </div>
  <Students students={studentsList} />
</div>
 );
}

Students component

function Students({ students }) {
return (
    <div>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Year</th>
            <th>Date of birth</th>
          </tr>
        </thead>
        <tbody>
          {students.map((student, index) => (
            <tr key={index}>
              <td>{student.name}</td>
              <td>{student.year.toString()}</td>
              <td>{student.birthdate}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

So here in this way even though the students' list is getting sorted, the state is not updating, but if I assign the initial list to the new one and then sort it and then update the state it is working.

WORKS

let sortedStudents = [];
Object.assign(sortedStudents, studentsList);
sortedStudents.sort((a, b) => new Date(b.birthdate) - new Date(a.birthdate));
//console.log(sortedStudents);
setStudentsList(sortedStudents)

DOES NOT WORK

studentsList.sort((a, b) => new Date(b.birthdate) - new Date(a.birthdate));
setStudentsList(studentsList);

So the question is why do I need to assign my studentsList to the new array, specifically by using Object.assign() so that setStudentsList() would update the component's state? I've just started learning React so it's really confusing me the way how these states actually work.

Similar posts I've found

FZs
  • 16,581
  • 13
  • 41
  • 50
Miraziz
  • 509
  • 7
  • 15
  • 4
    React determines if it should re-render or not based on the equality of previous props to the next props, and previous state to the next state. By mutating the original state array, the previous `studentList` has referential equality with the updated `studentList` and react will not detect that it needs to rerender. – Brian Thompson Dec 30 '20 at 16:21
  • `sort` mutates state and `assign` does not. – Dennis Vash Dec 30 '20 at 16:21
  • @BrianThompson Now it totally makes sense... Your comment answers my question. Please, let me know if I could improve my code because I'm not aware of best practices yet. – Miraziz Dec 30 '20 at 16:26
  • 4
    You don't want `Object.assign` here. You want `const sortedStudents = studentsList.slice();` (or any [alternative that copies the array](https://stackoverflow.com/q/7486085/1048572)). – Bergi Dec 30 '20 at 16:45
  • 1
    @Miraziz lol wouldn't call it "best practices" as much as "design limitations". I'm sure the React team would *love* to have the shorter version "just work" if they could do so reliably and with good performance :P – Jared Smith Dec 31 '20 at 14:40
  • @JaredSmith haha, yes I'm just learning React and I'd like to if I'm understanding and learning concepts correctly. – Miraziz Dec 31 '20 at 14:44

1 Answers1

0

As Brain mentioned: React determines if it should re-render or not based on the equality of previous props to the next props, and previous state to the next state. By mutating the original state array, the previous studentList has referential equality with the updated studentList and react will not detect that it needs to rerender.

So in order to change the list and to React detect the changes, the value of the array needs to be used (mutated), instead of its reference.

As Bergi hinted: alternative that copies the array by value

Miraziz
  • 509
  • 7
  • 15