Creating Sortable List with React, Redux, and Reselect

Will write simple React application with only one table and make it be sortable. But will keep sortable params inside Redux state and will use reselect lib to sort the collection.

Ресурс 2

543

Ресурс 1

11

The Internet is full of examples and solutions implementation of sorting in the collection, but as it often happens, the found ready-made solution does not entirely fit, as each developer tries to solve their specific problem. So at the end of the day, you may stop searching and write everything yourself, eventually getting not only the working code but also gaining over 9000 experience to self-confidence.

Here is my solution for this task, I implemented on one of our projects. This article is unlikely to be interesting for those who are already a dab hand at React and its ecosystem. It’s for those who have been familiar with it for a while and, just like me, once been looking for similar articles.

First I’ll write a simple component and then bring the logic outside. As I need, not only the component itself to know about the state of sorting but also any other component connected to the project.

Let’s add the ability to sort the list by any of the fields, for example, by First Name.

To do this, in the internal state of the App component, we define the collection for rendering and the sorting parameters; add a handler function that would change this collection. We will sort by clicking on the header of the First Name column.

// App.js...import{orderBy}from"lodash";importdatafrom"./shared/data";...constructor(props){super(props);this.state={collection:data,sortParams:{direction:undefined}};}handleColumnHeaderClick(){const{collection,sortParams:{direction}}=this.state;// Check, what direction now should beconstsortDirection=direction==="desc"?"asc":"desc";// Sort collection constsortedCollection=orderBy(collection,["firstName"],[sortDirection]);//Update component state with new datathis.setState({collection:sortedCollection,sortParams:{direction:sortDirection}});}...<thonClick={()=>this.handleColumnHeaderClick()}>FirstName</th>
...

I use the orderBy function because inside the project we widely use lodash, and it is just more convenient here than the native implementation.

Then we can complicate it and make a general sorting function by bringing in the field by which we want to sort:

At the moment we don’t pay much attention to the field type and sort by it as if it were a string or a number. But there is a date in our collection. And sorting by such a field will not work correctly since the data in the object is represented by a string. But, we will solve this “problem” a bit later 🙂.

Adding Redux and Reselect, made an App more complex

Our component perfectly solves the task. But, what if some other part of our App needs to be aware of how our list is now sorted? For example, you need to show outside the list component that it is sorted - to render some marker or icon, or you generally sort your list on the server, and then you need to transfer the sorting parameters to it. And here, obviously, we require that the state in which the parameters are stored to be external.

To do this, we add Redux to our App and put our collection in the initialState of the future store.

Now clicking on the table headers, we write the sorting parameters into redux state. And we can get them from any component subscribed to the state (precisely what we did in our component).

But now no sorting happens, because the component is transferred as props

collection:state.app.developersList

and it still doesn’t depend on parameters changing in the state. Let’s fix it.

We could leave the handleColumnHeaderClick function inside the main component and sort the collection in the same place. But we want our component to know nothing about which collection and how it renders.

Along with the selector, we wrote one helper function – orderByType. We can set how exactly the collection will be sorted via this function. For example, above, in setSortParams for the hiringAt field, we passed the date type as the second argument. And during the sorting, instead of a string in the “mm/dd/yyyy” format, we cast the date values to Date type and thereby solve the sorting mentioned above by date problem.

This can be useful if the data in the collection does not match its type. For example, the age or amount of salary is given as a string. And then setting

setSortParams("salary","float")

the data will be cast to the correct type.

That’s it! Now the component is subscribed to the state, where the collection comes from, but already rendered by the parameter-aware selector function. The only thing that remains inside the component is the action call with the transfer of a key and possibly a type to it.

What We’ve Done?

We’ve turned the App component out of the complex, and in the long run hard to expand and hard to maintain into an almost “clean” one and brought out all the logic that implements the sorting into its proper place:

The code in action is responsible for handling the click on the appropriate header and calling the reducer

Reducer puts data into a global state

Selector code is in charge of selecting data from the state and their sorting, depending on the parameters that came from the reducer

In this case, it is possible to change the behavior of the sorting function or the selector as much as you want - the component that renders the collection doesn’t need to be changed at all.