Rails, Reflex, and Dynamic Filtered Pagination

Michal Korzawski
eXpSoftwareEngineering
5 min readAug 15, 2022

Implementing HTML over the wire with combined search, filtered querying pagination via Ruby on Rails and Stimulus Reflex.

Pagination is a common feature on web apps and can be found everywhere from your first search on Google to a niche forum. It’s essential for viewing a large amount of data by dividing queried results into more organized and consumable sections, usually in the form of numeric pages.

Google’s pagination

However, pagination can be difficult to implement, especially when used in conjunction with other features such as search and filters. We also want our users to not have to wait for a page reload and to see their filters dynamically update, just as it would in a SPA. The good news is modern Rails doesn’t need something like React to do this; we don’t even have to venture outside of the Rails monolith!

We are early adopters of Stimulus Reflex and its dependency Cable Ready inside eXp-Realty’s Innovation Hub team. These packages allow real-time, HTML over the wire updates to the DOM. We use some form of pagination in every one of our web apps, and typically this is a variation on infinite scroll; which is a simple approach.

Recently, there was a request by the product team to replace our good old infinity scroll with numeric based pagination. Seems simple enough, although, there were a few special things to take under consideration:

  1. Allow search queries — when a user searches for “foo”, we need to paginate through a scoped collection
  2. On top of a search, support a lot (twelve total) of user dropdown selection and toggle filters.
  3. It all should work with Stimulus Reflex

Let’s take a look at the components.

Search, filters, and pagination view

The Search bar is an input field with a data-reflex attribute in it. The reflex points to results method in FilterReflex, which will be called on every user’s keyup event.

Search input and Reflex

The filters component is a form and its submit action is intercepted by the FilterReflex#results method.

Filter Component and form element with input and select tags

Lastly, there is a table displaying a collection of ActiveRecord items. I decided to go with the pagy Ruby gem and to abstract all the pagination logic. Pagy was a good choice due to its speed and extensibility.

Now we have all the pieces ready, so it’s time to assemble them together. Step one is to connect pagy’s pagination logic to Stimulus Reflex.

Pagy’s default implementation is to add a parameter, like ?page=3 to the url, and each time the pagination’s page numbers are clicked, the page will reload. This is not ideal for that snappy SPA feel we want, and to accomplish that, we shall call a reflex action every time a pagy page is selected.

Pagy’s navigation helper looks like this <%= pagy_nav(@pagy) %>. You can customize the tags this helper generates by adding the :link_extra attribute. In order to add a data-reflex action, our helper now looks like this:

Pagy view helper

Now every time you click on a pagy page, the FilterReflex#results method will be called. Great! But this is where the good news, and pagy’s magic quickly ends. Because now we have to manually tell pagy which page we want to visit.

Lets grab the page number first:

Configuring Pagy with Stimulus Reflex

The rest is just standard page table morphs via Stimulus Reflex to update the DOM. Lets add the filters to it. As mentioned and shown above, the filters are merely form fields being submitted to the server, and we can intercept these by using strong parameters inside the reflex.

Form’s strong params

Submitting filters and handling scoping for Active Record collections on the Reflex side works, but what about when the user clicks to go to page 2? Well, all of those applied filters on the collection will disappear. The reason for that is due to the fact that the filter info isn’t preserved anywhere, so going to a different page will reset whatever had been applied, despite the filter inputs still containing the values in them. In order to fix this short circuit, the filter data needs to be passed to the pagy helper. We do this by passing JSON with params to the collection view morph and modifying the pagy helper. In the FilterReflex#results method, you need to check for params-hash before checking filtered_params.

Passing form params to Pagy

Now the form’s filters will carry over to the new page when a user clicks “page 2”. This gets us to the point of having pagination working with Stimulus Reflex and filters. Search queries are the last piece of the puzzle, and search presents many of the same problems as filters did; data needs to be passed to the pagy helper somehow. The easiest, and most obvious, way would be to include it in the params_hash.

Adding search to params_hash

This naïve implementation works at first glance, but it has a serious flaw: The user starts to type in the search, deletes it, and goes to page 2. In this scenario, the results scoped by the first letter of the search query will remain. To fix that we need to distinguish when we are calling the FilterReflex#result method via search, and not elsewhere in the filters.

Add data-from-search-input

We need to now update query in the results method:

Updating query variable

Okay! This is working as intended with search and pagination simultaneously. However, when throwing filters in with search, all our hard work goes out the window.

Filters are simply inputs inside of a form, so we can add an additional hidden input there as a placeholder for the query.

Add in hidden field tag for query placeholder

Then in the FilterReflex#results method we need to morph this field and update it with the search query value.

Updating the input for search query

Almost there! The final order of business is to update the search input field with any data from the form’s filters. This is as easy as passing params_hash to the search input field as a data-attribute.

Final version of FilterReflex#result

That’s it. Now search, filters, and pagination is all connected under one glorious and true Stimulus Reflex method!

As you can see, it isn’t too easy to create an elegant pagination solution that works well for all users, but I hope you have found this article helpful in any way. Ideally, it may inspire you to create your own pagination solution or use a variation of mine.

--

--