The Rise of SPAs and the Return to Rails
Hey, welcome to the channel. In this video, we're going to look at how Hotwire works together with Stimulus and Turbo.
I've been around the web development scene for about 20 years now and I discovered Ruby on Rails around 2007 while searching for an alternative to Perl and PHP. And I've made a great living as a Rails developer ever since. I can't complain, it has kept me productive while delivering tons of value to customers in a reasonable time frame.
But around 2016, Ruby on Rails has started to lose its edge and there was a new kid on the block. It was a time when react.js has really picked up, and the big problem that React was solving was responsiveness, as in the page was faster to respond to user interactions. It started a new era of web development where instead of building one full-stack application, you now have two: one for the front end written all in JavaScript and another one on the backend written in your language of choice which acts as a REST API.
And seeing that Rails is drowning, but not being too convinced about this new single-page application movement, I started to look for alternatives. And I wasn't the only one; a lot of Rails developers jumped ship as well. A lot of us found this awesome language called Elixir, which at the time didn't solve that specific problem, but at least it seemed to be the next step forward for Rails developers.
Why Hotwire Is a Game Changer for Rails
And while using Elixir, I discovered LiveView, which is similar to Hotwire. I would tend to believe that's what inspired Hotwire. The selling point of LiveView was that you can achieve the same level of responsiveness without the complexity of building and maintaining two separate applications, that it could be done using regular old-style full-stack development without any JavaScript. So Elixir's LiveView and now Hotwire changed the game for me. I see it as a new way forward. And while it's not yet a complete replacement for single-page apps, I think it's a good enough alternative and a good reason to give Rails a second chance.
If you're interested in learning all there is to know about building applications with the latest version of Rails, I'm making a course for you, so make sure to check out this link here. Now let's see what Hotwire is all about.
Understanding Turbo Drive for Faster Navigation
So the old Rails used to render a new full HTML page in response to requests from the browser, right? So let's look at what happens when you click on a link or submit a form. As you can see here, when I click a link, the browser sends a regular HTTP request denoted here by this 'document' request type, and it receives a response from the server containing the full HTML page. So that's the old way.
But with Hotwire, if I click a link, you'll see that the request is no longer a type 'document', it's a 'fetch' request instead. Hotwire listens for these events and it converts them to AJAX requests, just like we see here. And then the head part of the response is merged with the one on the current page, so that if your assets didn't change, you avoid fetching them again. The body part of the response will replace the current page's body. So this is slightly better than the full page reload because you avoid some time-consuming requests for assets and you don't see the page reloading. This behavior comes from Turbo Drive, which is part of Hotwire.
Another thing that Turbo Drive does is it caches the response. If you visit the same page, Turbo Drive will use a cached version of the page to render a preview until the response comes in. And if you're using the back button, Turbo Drive won't make a request to fetch the old page at all because it can serve it from the cache. If you want to see how your Rails app behaves with Turbo Drive disabled, you can use this line. So that's what Turbo Drive does.
Partial Page Updates with Turbo Frames
But it gets better, a lot better. Replacing the entire page body might not be enough in terms of responsiveness. Sometimes you want to be more specific than that, and that's where Turbo Frames come in. They allow you to only update a particular part of the page by wrapping that part in a turbo_frame_tag. Any interaction within that Turbo Frame tag, like clicking a link or submitting a form, will be scoped to that frame, and the response from the server will be used to update just that one frame.
You'll notice that requests scoped to a Turbo Frame have a Turbo-Frame header with the ID of the frame. And if we take a closer look at the response, we'll see that it contains just the contents of that frame, which is a lot faster than sending the entire page over the wire. So we can render just the specific part of the page that we need to get faster updates.
But what if you don't want to render the response inside the frame? What if you want to navigate to the page the link points to, like a regular link? Remember that all the links inside the frame are scoped to that frame, so the response will be used to update the frame. If you just want to navigate to a different page, you can use a data attribute on the link to change its behavior.
Lazy-Loading Content in Turbo Frames
Also, you don't have to hard code the contents of the frame. You can fetch them from a URL by using the src attribute. In this example, I've removed the initial contents of the frame and I've added an src attribute pointing to the turboframe_test URL. And inside a template for that action, I've added a timestamp so we can see that the frame is updated. So on the initial page load, the content is loaded by fetching the URL, and when we click the button, a new frame is loaded. We can see that by looking at the timestamp. The timestamp is different for each of the requests.
Limitations of Turbo Frames
One limitation of Turbo Frames is that you can only update one frame at a time. So to illustrate this, let's add a second frame to the page and update the template to provide content for both frames at the same time. Each frame has its own DOM ID so we can target them separately.
Now, if I click the button inside the first frame, you'll see the contents of the first frame update, but the second one remains unchanged, even though the response contains the second frame as well. Only the one we clicked on updates. And the same goes if we click the second frame; it too updates while leaving the first one unchanged.
Updating Multiple Elements with Turbo Streams
But what if you need to update multiple elements on the page at the same time? Well, that is where Turbo Streams come in. With Turbo Streams, you can update multiple sections of the page and you have a little bit more control on how that's done. Turbo Streams use a different tag called <turbo-stream> and it takes an action attribute, a target, and a <template> container.
The action describes how you want to update the target. You can update its contents or replace it completely, or append or prepend to it, or remove it from the page, or add a response before the element or after it. The target is the ID of the element you want to update, and the template is the content you will use to update the target.
Something to note, though, is at the time of this recording, Turbo Streams don't work with GET requests. We can only use them with forms. Rails injects a new MIME type of turbo-stream into the request's Accept header. In the Rails logs, you can distinguish Turbo Stream requests from the others by the turbo_stream format, which you can use in the controller inside of a respond_to block. So say if you want to respond to JSON requests, you can do that.
Real-Time Updates via WebSockets
You can even stream updates directly from the model over multiple browsers via WebSockets. So we changed this to subscribe to updates on this notes_room channel and then we render our model partial inside this generated DOM ID. Then we can broadcast updates to the model and they will be picked up by everyone that subscribed to the channel. The @note instance variable is defined in the index action. It simply finds the first note in the database. So let's update the model in a different window and see how the changes are picked up in the other one. That's pretty cool.
A Primer on Stimulus for Client-Side JS
Lastly, let's talk a little bit about the second component of Hotwire, namely Stimulus. With Stimulus, you can add some JavaScript to your page if you need it. So Stimulus is not meant to replace something like React, for example. I see it more like a replacement for jQuery, but that's probably not a fair comparison because Stimulus doesn't really do that much. What it does, though, is it offers a simple way for you to hook into user-sent events like clicks, scrolls, key presses, drag and drop, etc. And the way it does that is through controllers and actions, which is just another name for objects and methods.
To create a Stimulus controller you can use a Rails generator. It will create a controller file and update the index.js file to import and register the controller for you. Inside the controller, you'll see a new JavaScript class that extends the Stimulus Controller class, and it also creates an empty connect callback for you. The connect callback is automatically called when the controller is connected to a DOM element. You also have callbacks for initializing the controller, which is called once, and for disconnecting the controller from the DOM. But you can create a custom action as well, so let's create one just for fun.
As you can see, we now have a home_controller.js file that defines a new class, and we can add a method to it. Okay, so now we have this method called myMethod and we want to use it. So in the index.html file, I'm going to connect the Stimulus controller to the DOM by adding a data attribute to the div that wraps this link. And just by doing that and reloading the page, I can see that the connect callback is being fired. But now I want to use my new custom method, and in order to do that, I have to use a different attribute. This one is called data-action, and it points to the home controller and the myMethod action. Now if I go ahead and click that link, I'll get my message in the console. That is because I used the click handler in my a data-action attribute.
And you can do a lot more with Stimulus, but that is beyond the scope of this video. If you'd like to see something more involved, I made a video about how to create a drag and drop feature using Stimulus, so you can check out that video, which I'm going to link in the description.
Conclusion & What's Next
So that's a somewhat high-level overview of what you can do with Hotwire. If you're interested in learning more about it, make sure to subscribe to the channel because I'll be posting a lot more videos related to all the new features in Rails. Bye.