reactjsarraysreact-state

Not able to sort an array of objects initiated using the useState hook


I have an array of items declared using the useState hook:

const [items, setItems] = React.useState([{id: 1, title: "Example Item"}, {id: 2, title: "Another example item"}])

I have mapped the values of this array using the items.map function:

  <ul>
    {items.map(item => (
      <li key={item.id}>{item.title}</li>
    ))}
  </ul>

I have a couple of buttons to sort the array of items:

<p><button onClick={sortByTitleAscending}>Sort by ascending</button></p>
<p><button onClick={sortByTitleDescending}>Sort by descending</button></p>

I use the setItems function to update the list of items after sorting it on onClick of the button.

const sortByTitleAscending = () => {
  setItems((prevItems) => {
    const sortedItems = prevItems.sort((a, b) => a.title.localeCompare(b.title));
    console.log(sortedItems);
    return sortedItems;
  });
}

const sortByTitleDescending = () => {
  setItems((prevItems) => {
    const sortedItems = prevItems.sort((a, b) => b.title.localeCompare(a.title));
    console.log(sortedItems);
    return sortedItems;
  });
}

The following is a stack snippet of my code:

function App() {
  const [items, setItems] = React.useState([{id: 1, title: "Example Item"}, {id: 2, title: "Another example item"}])

  const sortByTitleAscending = () => {
    setItems((prevItems) => {
      const sortedItems = prevItems.sort((a, b) => a.title.localeCompare(b.title));
      console.log(sortedItems);
      return sortedItems;
    });
  }

  const sortByTitleDescending = () => {
    setItems((prevItems) => {
      const sortedItems = prevItems.sort((a, b) => b.title.localeCompare(a.title));
      console.log(sortedItems);
      return sortedItems;
    });
  }

  return (
    <div>
      <h1>Hello World!</h1>
      <p><button onClick={sortByTitleAscending}>Sort by ascending</button></p>
      <p><button onClick={sortByTitleDescending}>Sort by descending</button></p>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>

As you can see, when you run the code and click on "Sort by ascending" or "Sort by descending", the mapped list doesn't get updated, but the list of sorted items is shown in the console.

I would like to know why this happens and also what is the correct way to sort a list of mapped items.


Solution

  • The issue with your code lies in the sorting operation:

    const sortedItems = prevItems.sort((a, b) => a.title.localeCompare(b.title));
    

    Here, you're operating on the original prevItems array by sorting it in place (Array.prototype.sort() gives a reference to the modified array). When setItems is called with the sorted array, React does not recognize that the state has changed because the reference to the array remains the same. This results in the UI not updating to reflect the sorted order.

    You can solve this by copying the prevItems to ensure you're not dealing with references anymore but operating with the array values:

    const sortedItems = [...prevItems].sort((a, b) => a.title.localeCompare(b.title));
    

    You can read more about state immutability in the React Docs. Hope this helps!