I am sure you have built many item listings on an index page with filters, sorting, and an option to add a new item. We can do all these things using two very popular methods:
StimulusReflex is a library that enhances the possibilities of Stimulus and Rails - it helps backend developers create reactive applications. Using StimulusReflex and Rails, Reflex can intercept user actions and send them to Rails in real time using WebSockets. Interactions are handled via Reflex Actions that change an application state - Reflex Actions are very similar to the Rails controller. The current page is quickly re-rendered and the changes are sent to the client using CableReady, then the page is morphed (by the morphdom module). According to Reflex’s creators, the whole operation should take around 20-30ms and should not be noticed by the end-user.
Reflex’s creators claim that "This architecture eliminates the complexity imposed by full-stack frontend frameworks without abandoning high-performance reactive user experiences" and that Reflex is a fresh alternative to the Single Page App.
Even if this sounds too good to be true, thanks to Stimulus and StimulusReflex, applications written in Rails can have a new life with a little code and work. Both seem like can be very useful with small and medium applications, helping backend developers create dynamic, reactive applications.
This example is a simple table with comments. The theory is that clicking the Add comment button adds a new record to the database and then adds a new one to the table dynamically, without page refreshing. It looks like this:
The code for the above view is following:
To add Stimulus to our code we have to use special data tags which you find in the documentation. It requires some additional steps:
addComment is triggered via the action which we declared at the button (data-action="click->comments#addComment”). Easy, right?
As you can see, Stimulus is a bit like an interface thanks to which we can manage the logic of scripts very easily. But we can do it much easier - without fetching data via an API or preparing and inserting HTML parts. Let’s see how it looks using StimulusReflex.
Our HTML won’t be changed a lot. The only thing is that <div id=”comment_items”> does not have to be a target because elements will be added dynamically from the Rails Controller. However, our JS controller will look much different.
It means that the addComment method calls "create" in the CommentsReflex class via the .stimulate() method provided by StimulusReflex. The second parameter is a value from the input which we want to send to the create method. create is declared in the app/reflexes/comments_reflex.rb file.
That’s all. StimulusReflex does not require anything more. The Rails controller looks very simple (code below) and the effect is very impressive.
We can freely extend the CommentsReflex class with new functionalities which could be returned on the site - for example you could set new flash messages and inform users about the status of an operation dynamically.
The final goal was to have this (while scrolling down):
Instead of this:
This example is pretty basic, but it shows nicely how Stimulus helps you with code organization.
Our erb template looks like this:
As you can see, attaching HTML elements to the Stimulus controller is super easy, and so is specifying the targets - thanks to passing them to the JS controller, we can manipulate them.
Let's see what's inside our InfiniteScrollController.
`targets` are our list of all targets we specified in our HTML view. Later in this controller, we will be able to refer to them as `paginationTarget`.
The major benefit we get from pure Stimulus is a readable template with JS connected to specific HTML elements, as well as clear targets that we send to the Stimulus Controller.
More fun awaits us with Stimulus Reflex examples.
Let's move to the fun part - StimulusReflex. This example will show a way of implementing pagination (classic approach this time, not infinite scroll) using Pagy, but without page reload.
Let's start by connecting our table with the Stimulus controller using the data-controller attribute. We are also telling StimulusReflex that our DIV is a root and it should reload only its content, but nothing outside of it - data-reflex-root="#dashboard-table".
Let's move to the StimulusReflex Controller. In its basic form it looks like this:
To have a full view, here is our Rails controller's action for this view.
And that's it - with this setup, our backend provides us with a paginated collection of items. Stimulus and StimulusReflex extract and send the desired page number to the backend. Backend fetches the page and returns it via CableReady to Stimulus Reflex. StimulusReflex morphs the page giving us the final result - pagination without reloading.
Marcin Zgórecki & Kamil Chodorek