DEV Community

Cover image for React: how to dynamically sort an array of objects using the dropdown (with React Hooks)
Katsiaryna (Kate) Lupachova
Katsiaryna (Kate) Lupachova

Posted on • Updated on

React: how to dynamically sort an array of objects using the dropdown (with React Hooks)

Assume we have the following problem:

  • sort an array of objects
  • do it dynamically based on different properties values
  • render this in browser using react.js

OK, let’s get down to business!

The sample array of objects:

const bands = [
  {
    name: 'Nightwish',
    albums: 9,
    members: 6,
    formed_in: 1996,
  },
  {
    name: 'Metallica',
    albums: 10,
    members: 4,
    formed_in: 1981,
  },
  {
    name: 'Nirvana',
    albums: 3,
    members: 3,
    formed_in: 1987,
  },
];
Enter fullscreen mode Exit fullscreen mode

For the sake of this tutorial I’m not going to do any fancy components, so let’s render this array in plain div’s.

function App() {
  return (
    <div className="App">
      {bands.map(band => (
        <div key={band.id} style={{ margin: '30px' }}>
          <div>{`Band: ${band.name}`}</div>
          <div>{`Albums: ${band.albums}`}</div>
          <div>{`Members: ${band.members}`}</div>
          <div>{`Year of founding: ${band.formed_in}`}</div>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Check the view in browser:

bands

Looks good!

Now let’s add the select element with options of sortable properties.

<select>
        <option value="albums">Albums</option>
        <option value="members">Members</option>
        <option value="formed">Formed in</option>
</select>
Enter fullscreen mode Exit fullscreen mode

That’s fantastic, but absolutely nothing happens when we change the dropdown options.

dropdown

To fix this problem we need to somehow connect the select element and the array which we want to sort and re-render sorted array values each time the different select option is chosen.

According to React’s docs:

By default, when your component’s state or props change, your component will re-render.

That means that we have to add state to our component. And I’m going to do it with the help of React Hooks.

Let’s define the state variable data and the method for its update setData using useState hook.

const [data, setData] = useState([]);
Enter fullscreen mode Exit fullscreen mode

Hypothetically, when the state will be updated with the new data (sorted array), the component should re-render. To test it we need to define a function that will sort the bands array based on the selected option in the dropdown and call it every time the selected option changes.

...
const sortArray = type => {
    const types = {
      albums: 'albums',
      members: 'members',
      formed: 'formed_in',
    };
    const sortProperty = types[type];
    const sorted = bands.sort((a, b) => b[sortProperty] - a[sortProperty]);
    console.log(sorted);
    setData(sorted);
  };

...
<select onChange={(e) => sortArray(e.target.value)}>
...
Enter fullscreen mode Exit fullscreen mode

But when we run the code, it's not working properly.

members-sort

albums-sort

Array is being sorted just fine, as it’s printed in the console, but the array data doesn’t re-render. It only renders when we change the sorted value for the first time.

The problem is in the following, as it’s stated in the React docs:

Do Not Modify State Directly

So, this line of code is wrong, as it modifies state (sorts the array, which is in the state) in place. And React “thinks” that setData is being called with the same array that it already had, therefore no re-render. (Big “thank you” goes to T.J. Crowder who helped me to clarify this problem)

const sorted = bands.sort((a, b) => b[sortProperty] - a[sortProperty]);
Enter fullscreen mode Exit fullscreen mode

The right way is first to do the copy of the bands array, sort it and then call setData with this array. So, just adding the spread operator to copy array should solve the problem.

const sorted = [...bands].sort((a, b) => b[sortProperty] - a[sortProperty]);
Enter fullscreen mode Exit fullscreen mode

Let’s try to run the code. Well, it kinda works, but the bands data doesn’t render on the start, just after the select option is changed.

The problem could be easily solved with the help of useEffect Hook.

1.Define another state variable for storing value of the sort property. By default, the bands array will be sorted by a number of albums.

const [sortType, setSortType] = useState('albums');
Enter fullscreen mode Exit fullscreen mode

2.Update the value of the sortType on select option change.

<select onChange={(e) => setSortType(e.target.value)}>
Enter fullscreen mode Exit fullscreen mode

3.Add useEffect Hook, which will call sortArray function after the component renders and then every time on update of the sortType value. We achieve this by passing the second argument (sortType) to useEffect that is the array of values that the effect depends on.

useEffect(() => {
    const sortArray = type => {
      const types = {
        albums: 'albums',
        members: 'members',
        formed: 'formed_in',
      };
      const sortProperty = types[type];
      const sorted = [...bands].sort((a, b) => b[sortProperty] - a[sortProperty]);
      setData(sorted);
    };

    sortArray(sortType);
  }, [sortType]);
Enter fullscreen mode Exit fullscreen mode

Now the code works as expected!

The complete source code is available in this GitHub repository

Originally posted on my own blog

Discussion (12)

Collapse
muyembeii profile image
MuyembeII

Hello, thank you for this great tutorial. Could you help with sorting nested object. E.g location has nested fields city and town and lets say I wanted to filter by town

const bands = [
{
name: 'Nightwish',
albums: 9,
members: 6,
formed_in: 1996,
location: {
city: "Berlin",
town: "Cologne"
}
},
..............................................
];

Collapse
ramonak profile image
Katsiaryna (Kate) Lupachova Author

Hi, Muyembell! Thank you for your feedback!
As for your question, you can sort array of objects by nested property this way (if you need to compare strings):

array.sort((a,b) => a.location.city.localeCompare(b.location.city));

Please, check the full updated code of the tutorial example with nested property sorting here - branch nested-sorting.

Collapse
muyembeii profile image
MuyembeII

Thank you 🙌. Worked like a charm

Collapse
encryptblockr profile image
encryptblockr

is it possible to have this sort as a component? so one can use on multiple pages? thanks

Thread Thread
ramonak profile image
Katsiaryna (Kate) Lupachova Author

yes, of course, it's totally possible. Just pass initial data and and sort type as a props to this component. The exact implementation depends on your use case.

Collapse
codeindevelopment profile image
Code in dev

thanks a lot men , this code very very helped me for sort options in my project <3

Collapse
udittarini profile image
Udit Tarini

Hey thank you it worked really well

Collapse
luisregardiz profile image
Luis Garcia Regardiz

Thank u, it was very helpful 👍

Collapse
encryptblockr profile image
encryptblockr

codesandbox link?

Collapse
ramonak profile image
Katsiaryna (Kate) Lupachova Author
Collapse
encryptblockr profile image
Collapse
ramonak profile image
Katsiaryna (Kate) Lupachova Author

Please check the answer - github.com/KaterinaLupacheva/react...