Filtering, Sorting and Pagination – Advanced Filtering with React and Redux

Advanced Filtering with React and Redux

Advanced Filtering with React and Redux

Filtering through objects and text is perhaps one of the most common features any consumer-facing app is going to have. From blogs to e-commerce apps and SaaS platforms, whenever you need visitors to find something quickly on your site, you will have to build some form of filter.

These filters vary in terms of complexity. The simplest ones only require a search bar and rely on the plain Javascript API to filter through documents. More advanced filters may use a combination of search inputs, checkboxes, and range bars to find whatever the visitor may be looking for.

Even more advanced applications may rely on making requests to a server and simply serving the results. These rely on a combination of technology, perhaps using platforms like elastic search and other complex methodologies such as semantic or full-text search.

But how do you decide whether carrying out these operations entirely on the frontend is a good choice?

Why carry out filtering and pagination on the frontend?

You may have noticed from above that filters/search can be divided roughly into two categories – frontend and backend search.

As the name suggests, search functionality carried out entirely on the frontend usually involves querying data from the server and filtering through it using JavaScript. This approach involves querying your whole dataset and has a few upsides:

  • It does not require extra loading, so sorting data and other operations are almost instantaneous.
  • Fewer requests are made to the server.
  • Any operation carried out, e.g. map, reduce, filter, is done on the whole dataset.
  • The downside is that since all the data is queried at once, it doesn’t work very well for extremely large datasets. With millions of records, it would take a significant amount of time to process various operations.

What we’re building

It’s the most suitable method for static sites and blog posts, so our application is going to rely entirely on the frontend approach. It is going to be a simple e-commerce application that allows you to sort alphabetically and by price then apply other filters such as color and brand.

The dependencies

This guide assumes you’re comfortable with both React and Redux but the steps will be broken down where things get a bit heavier. The frontend also relies on Bulma for styling, but that’s purely an aesthetic choice. Any other library will do.

Additionally, you should also be familiar with ES6 functions such as sort, filter, and find. If not, check out this refresher into an array iteration.

With that, let’s get started by installing our dependencies

A brief introduction to Redux

Redux is a state management library for modern web apps. It can be used with any library and its job is to simplify how developers manage data displayed to users.

Its basic tenets are as follows:

  • The application has a single central state – the source of truth.
  • The state is read-only. Modifying it will lead to some pretty quirky bugs.
  • Changes have to be made with pure functions.

The foundational concepts behind Redux are actions and reducers.

Actions are plain Javascript objects that describe what happened. Think of them like breadcrumbs describing the path you followed before firing an event.

An action in a bread-buttering web app might look like:

Reducers logically come next after actions. Once the action describes what the application is expected to do (or has done), reducers take in the current state and action, modify the state and return the newly modified state.

A reducer is a simple function that might look like this:

Notice how we use Object.assign to create a new instance of the state rather than mutate the initial state. Redux state should never be modified directly.

Setting up Redux

First, we need to set up Redux so that it can communicate with our main application. This involves creating a store and wrapping our main application with a Redux provider. It can be achieved as follows:

At our entry point (index.js if you used create-react-app):

Next, let’s create our reducers and actions. Create a file – ‘src/store/index.js’.

The actions we are going to create to start with are as follows:

Our actions are simply functions that return an object. Everything within the action will be passed to the reducers.

Speaking of reducers, this is what our reducers look like so far:

They don’t really do anything, but we’ll add some more details as we go along.

With that said, Redux is now fully set up. Let’s set up the frontend.

Creating the core framework

Bulma is a CSS framework much like bootstrap. It has a very small learning curve, so the details of what every selector will be skimmed over.

First, let’s make a small change to loadData. We will use a utility function included in the Github repo to generate random data and change our method to:

And we’ll change our App.js file to load these generated products.

In the browser, that translates to

Browser result

Browser result

Filtering documents with React and Redux

The first major functionality we are going to add is filtering products according to the name and designer. Whenever there’s any input in our search box, we’ll only return products that have the input value in either their ‘name’ or ‘designer’ fields.

We start off by listening to changes in the search box.

And inside the filterByInput function, we only dispatch our action:

Before we proceed, let’s have a gameplan of what’s supposed to happen once the application receives some form of input to filter.

  1. User types inside the input box.
  2. We dispatch the ‘FILTER_BY_VALUE’ action.
  3. Our application filters through the products and returns a list of any products that match the input.
  4. The view is re-rendered automatically.

Pay particular attention to step #3 as we proceed.

The first iteration of this functionality might look like this:

And sure enough, that should work. There’s just one problem though – do you notice anything when the input is being erased?

Input problem

Input problem

This happens because of step #3 like we’d outlined before. When the ‘FILTER_BY_VALUE’ action is fired, the ‘products’ array is changed to only contain objects that match our search parameters. In order to get it to work even when the input is erased, we need a way to track whether a filter has been applied or not. If an empty string is passed as the action, it probably means everything has been erased and there are no filters applied.

Let’s make a few changes.

And we’ll also need to change our view to match.

And, Voila! The filter function should work perfectly now.

Filter function

Filter function

Sorting through documents with React

This part doesn’t need much explanation. We want to sort the array alphabetically both in ascending and descending order by name and by price.

Every Javascript array has a sort function that accepts a predicate/function. This predicate has two values as the parameters – the current and next value (a & b).

  1. If compare (a,b) is less than zero, a is sorted to a lower index than b. i.e. a will come first.
  2. If compare (a,b) is greater than zero, b is sorted to a lower index than a, i.e., b will come first.
  3. If compare (a,b) returns zero, a equals b and their positions will be unchanged.

So, to sort alphabetically in ascending order (A-Z)

But we can create a utility function so we don’t have to create dozens of different methods:

And to sort in descending order:

Then we can change the code under the ‘SORT_BY_ALPHABET’ case to:

Which should work perfectly well.

Sorting

Sorting

Along the same vein, sorting by price should work just as well

Sorting by price

Sorting by price

Now, onwards to the final part of this guide.

Paginating objects in React

The final phase of this project, and by far the most complex, is adding a bar to the site with a list of all the pages through which our documents can be accessed. If you have, say, 100 documents, it might be a bit unwieldy for the user to view all a hundred of them at once, so we load 20 of them at a time instead.

First, let’s add the component to the view:

From the above:

  • When the ‘next’ button is clicked,  it should take us to the next 20 documents.
  • The ‘previous’ button should serve the previous 20 documents.
  • ‘[…Array(this.props.state.filteredPages)].map((value, index) =>’ creates a new array with a length defined by ‘filteredPages’ and maps the pagination components to them. We’re getting to ‘filteredPages’ in a little bit.
  • When any of these pagination components is clicked, they should load an exact page.

The implementation of ‘nextPage’, ‘previousPage’ and ‘goToPage’ simply dispatch the corresponding actions.

Of course, we also have to create these actions

And here’s what it looks like:

Pagination

Pagination

In order to get the pagination to work, we have to change quite a bit of code from what we’ve written before, so there will be a lot of jumping back and forth. Whatever file we’re working on will he highlighted for that reason.

We’ll start off on the logic bit by adding a few details when the page is first loaded. Each new detail is explained in the comments.

The core of all this functionality happens within the ‘LOAD_NEW_PAGE’ case. I prefer to have fewer cases to deal with in my switch, so I use the same one for loading both the previous and the next page. Here is what that implementation looks like

That should result in:

Pagination result

Pagination result

And finally, in order to load the exact page in question, we’ll need some more code. A lot of this works the same way as previous pieces of code already highlighted.

Which brings us to the close of this project:

The result

The result

Notes

  • Once you play around with the project a little, you might notice a glaring bug – pagination doesn’t work properly with filters applied. In the interest of keeping this article brief and engaging, that’s been left out.
  • Despite using query parameters throughout the project, they aren’t relied on too extensively. In a production app, they should be referenced every time a page is loaded before serving up the documents.
  • ‘window.history.pushState’ is called within the main state container. This is an antipattern. Anything that changes the view directly should not be called outside actual components. This would normally be done with ‘redux-thunk’ but, again, this guide would be too long otherwise.
  • Nothing restricts users from browsing all the way past the currently available pages – both in the positive and negative directions.

Resources

Github: https://github.com/Bradleykingz/react-filters-pagination

Codesandbox: https://codesandbox.io/s/nice-hugle-q6yyz?fontsize=14&hidenavigation=1&theme=dark

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

26.03.2024

An Introduction to Clustering in Node.js

Picture your Node.js app starts to slow down as it gets bombarded with user requests. It's like a traffic jam for your app, and no developer likes ...

15.03.2024

JAMstack Architecture with Next.js

The Jamstack architecture, a term coined by Mathias Biilmann, the co-founder of Netlify, encompasses a set of structural practices that rely on ...

Rendering Patterns: Static and Dynamic Rendering in Nextjs

Next.js is popular for its seamless support of static site generation (SSG) and server-side rendering (SSR), which offers developers the flexibility ...

1 comment

Muhammet Al-Dulaimi July 19, 2021 at 11:26 am
0

Made an account just to let you and other readers know you could make a better filter by input using a couple lines, and that function returns the new results even when the user backspaces his input
let newState = Object.assign({}, state);
let value = action.payload.value.toLowerCase();
let filteredValues = initialState.productData.filter(obj => obj.name.toLowerCase().includes(value))

return {
…newState,
productData: filteredValues
}

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy