RailsConf 2023

Hotwiring My React Brain

Hotwiring My React Brain

by Aji Slater

In the video titled "Hotwiring My React Brain," Aji Slater discusses the transition from React to Rails' Hotwire framework during their presentation at RailsConf 2023. Slater addresses the common struggles faced by developers who are accustomed to building React applications and are now exploring Hotwire for full-stack development.

Key points discussed include:

  • Personal Journey: Slater shares their personal experience of struggling to adapt to Hotwire after years of working with React and GraphQL, realizing the need to modify their mental models of web development.
  • Understanding Hotwire: An overview of Hotwire as part of Rails 7, broken into its two main components: Turbo, which enhances links and form submissions for partial updates, and Stimulus, which allows JavaScript interactions with HTML.
  • Mindset Shift: Emphasis on the fundamental differences between React’s component-based architecture and Hotwire’s server-driven approach, highlighting that traditional React problem-solving techniques do not directly apply when working with Hotwire.
  • Building with Hotwire: Slater walks through practical examples of building a web application with Hotwire, discussing Turbo Frames and their usage for scoping navigation and avoiding unnecessary JavaScript.
  • Real-time Functionality: Demonstrations of integrating real-time interactions with Hotwire, like inline editing and real-time updates, showing how these are handled differently than in traditional React applications.
  • Functional Examples: Slater presents a series of coding examples that showcase how to implement functionality in a Rails application using Hotwire, including turbo frames for partial updates and stimulus for client-side interactions.

In conclusion, the video underscores the importance of adapting one's mental model when transitioning to Hotwire from a React-centric approach. The main takeaway encourages developers to embrace the Hotwire framework as a powerful tool for building interactive applications with less JavaScript, emphasizing simplicity and server-generated HTML.

Through Aji Slater's insights, viewers are equipped with a clearer understanding of how to navigate this transition effectively, advocating for a blend of old-school Rails principles with modern interactive web experiences.

00:00:18.800 Good morning, cats and kittens! My name is Aji.
00:00:24.779 My pronouns are they/them, and I work at a place called Thoughtbot.
00:00:31.560 I usually tell a little silly fact about myself at the start of my talks, but this time I'm going to be a little more real about it.
00:00:37.020 I'm still new to this Hotwire stuff. I was up late rewriting this talk all last week because I had a Hotwire suggestion while pairing.
00:00:42.719 The response I got was, 'Oh honey, no.' My pair didn't say that, but that's how I heard it as they linked me documentation about just how wrong I was.
00:00:50.039 But also, something clicked, right? I got a little further along in my understanding than I was before.
00:00:56.039 If only my anxiety would learn that embarrassment at work usually leads to being so much smarter afterwards, I could look forward to it instead of like this.
00:01:01.980 I feel like I'm having a harder time wrapping my head around this than everybody around me. I think it's because I'm so hardwired to think in React.
00:01:07.200 Every moment where I've taken a leap in my skills with this whole Hotwire thing has been because another thought pattern that served me well in React land has fallen away.
00:01:13.500 I tell you that because I'm trying to save a few people who are making a similar change in their day-to-day tooling or code bases a little bit of that cringe.
00:01:19.020 A little bit of that 'Oh honey, no.' I already did it so you don't need to do it too.
00:01:25.080 But like many of us, if not all of us here, I identify as a Rails developer.
00:01:31.680 Ruby is the first programming language I truly learned; I do not count BASIC from back when I was 12.
00:01:37.979 But like many in the Rails world, I have spent the last few years using Rails not as a full stack application framework, but in API mode.
00:01:43.799 I’ve been sending JSON over the wire to various versions of a React frontend.
00:01:51.180 Hotwire or React? I'm not going to say that one is better than the other.
00:01:57.659 I'm barely, if at all, going to spend time highlighting strengths and weaknesses or talking about trade-offs. This talk isn't to convince you to switch.
00:02:03.780 We all know that it's trade-offs all the way down, and the frustrating answer to every question is, 'It depends.'
00:02:09.599 The moment in question today is beyond that decision anyway. The application you're building or are about to build will be written without React, using nothing but Rails, Hotwire, Turbo, and Stimulus.
00:02:15.239 Maybe Turbo Native, but that's a different talk.
00:02:20.580 If you've written a lot of React lately and are new to this Hotwire world...
00:02:26.099 If you think in components and hooks rather than Turbo Frames and Stimulus controllers, you are where I was six months ago and you are who I'm here for.
00:02:31.440 And if you already know a little bit about Hotwire, just don't heckle me during the entry-level stuff, okay?
00:02:36.480 If you're unfamiliar with the players, let me introduce you before we get too far along.
00:02:43.620 Hotwire is, as of Rails 7 in 2021, the framework's built-in solution for the frontend.
00:02:49.800 It's made up of two parts: Turbo and Stimulus.
00:02:56.040 Turbo is the successor to Turbo Links. It accelerates links and form submissions by capturing interactions and submitting via JavaScript.
00:03:01.500 This allows for partial updates and independent loading of portions of the page.
00:03:08.239 Stimulus is a system that allows for graceful interaction between narrowly scoped JavaScript controllers and our HTML.
00:03:15.060 For those interactions that need to exist completely on the client side.
00:03:23.099 And before six months ago, these were not in my wheelhouse.
00:03:29.099 Okay, I love React. I know that is a weird thing to say at RailsConf, but I really do!
00:03:34.620 Like Rails, it just fits with how my brain works.
00:03:40.260 The declarative UI, reacting to changes in the state of a system made of component trees, with data down and actions up.
00:03:47.640 And sure, it's produced by a shady mega-corporation.
00:03:53.519 But it's not like it's the only place in tech where we love a technology despite its founder. On top of React, let's add in GraphQL, which I also stand by.
00:04:05.519 Even our much-beloved backend starts to look less and less like Rails, with essentially no controllers and only a single endpoint.
00:04:14.840 Even though my current situation leads me to write a talk about how to use Hotwire and leave React, I'm pretty sure I still have more fun writing React.
00:04:22.620 It just fits with how I conceptualize the front end and complex UIs.
00:04:28.800 This is what made the transition for my current project particularly difficult.
00:04:36.000 This project is a greenfield application, lots of interactivity with the user including chat and real-time updates to information that have that real SPA kind of feel.
00:04:43.680 Well, what is SPA anyway? Webster's Dictionary defines it as a commercial establishment providing facilities devoted especially to health, beauty, and relaxation.
00:04:49.560 But my client is in health insurance, so none of that is covered.
00:04:56.760 When we talked about SPA, it meant single page application. It's such a broad term; it can mean anything from the literal web app implementation that loads from a single web document and updates the body content.
00:05:03.060 That is from MDN, to describing the experience that users have come to expect: a fast, responsive, and interactive web application that provides a smooth and engaging user experience with real-time updates, mobile responsiveness, and fluid interactivity.
00:05:10.800 This one is from me and chat GPT; it's like a 50/50 collaboration.
00:05:15.060 So giddy at the opportunity to not pull in a front-end framework.
00:05:22.840 Both my team and my clients were excited, confident that Hotwire would bring that SPA browser feel.
00:05:29.579 And despite my fondness for React, I was excited too!
00:05:37.380 Server-rendered HTML, just like Mom used to make!
00:05:43.680 It's an idyllic return to the first principles of the web. Hotwire sounded analog and peaceful after years of JavaScript loading up on new features like toilet paper in 2020.
00:05:50.520 What I didn't appreciate at the time was how different the problem-solving techniques and patterns were truly going to be.
00:05:56.220 So we're getting to the heart of my motivation for being up here with you.
00:06:03.600 A mental model is an explanation of someone's thought process about how something works in the real world.
00:06:10.500 So I want us all to think of the solar system.
00:06:15.600 Think of the solar system. You probably conjured up something like this, right? This picture of planets orbiting the Sun probably reflects the mental model of most.
00:06:22.260 Because this representation is good enough for our day-to-day interactions with Jupiter, the representation we carry will influence how we approach problems.
00:06:30.060 An incorrect mental model led Senator Ted Stevens to claim that streaming video had clogged the internet's series of tubes, causing his emails to arrive days late.
00:06:36.720 For us here in this room, the wrong mental model can leave bugs in places we would never even think to investigate.
00:06:46.020 And although React and Hotwire are solving the same sorts of problems, the metaphors, concepts, and building blocks that make up the tools we use to solve those problems aren't really compatible.
00:06:54.300 And I wasn't going to be a Ted Stevens in this project forever.
00:07:02.280 So I'm going to describe my mental model of working with React.
00:07:09.660 I bet it's pretty close to how many of us think in that system.
00:07:15.060 It's a tree of components. The rendering of this tree starts at the root; that's usually App or something similar.
00:07:22.440 Each component along the tree has the ability to render DOM elements, nothing, or more components.
00:07:30.120 The components deal in data, manipulating it or passing it to child components as props.
00:07:36.960 Data can only flow down the tree from parent to child.
00:07:43.140 Data flow is fundamental as a React developer, and it gives us one half of that React mantra: data down.
00:07:50.400 In this model, as we're looking at it, network requests are irrelevant.
00:07:56.700 I bring this up because trying to equate them between this model and Hotwire will only be confusing.
00:08:03.120 Okay, but it's web development, right? How can network calls be irrelevant?
00:08:10.140 Let's focus on this component here for a moment.
00:08:15.600 The component gets some props, uses this data in a function to derive new state, and uses that state to conditionally render children.
00:08:22.560 Now let's say that this component gets some props, uses this data in a network request that gives it new state, which it uses to continually render children.
00:08:28.440 They're functionally equivalent, so there's nothing you can do from elsewhere in the tree to determine if that change was local, a hook, user input, or even just hard-coded.
00:08:36.360 So, for our purposes today, forget the React Network calls and don't try to find equivalency with Rails.
00:08:43.080 The last piece to this model is callbacks.
00:08:49.680 As with data, a component can send a function down the tree which can later be invoked by a child component.
00:08:55.560 That is the only way to return data back up the tree to components higher in the order.
00:09:02.040 And that's where we get the second half of that React developer mantra: actions up.
00:09:08.520 Those are our building blocks of a React application: the components, the state, and the actor.
00:09:15.279 In this system, the state drives us forward. A change in state causes a reaction in the component tree, and we can think about how to move state around the tree by remembering: data down, actions up.
00:09:29.340 This was the model that I had been working with for years.
00:09:36.960 The solution that came easily to me started with trees and state, but Hotwire was impenetrable until I could leave the old problem-solving models behind.
00:09:43.680 Conference talks, I find, are not particularly great for picking up on syntax and specific technical details.
00:09:51.300 So unless you're watching this virtually from the future (in that case, hello! Rails greater than seven is pretty great, isn't it?), I'm going to get through as many examples as I can.
00:09:59.700 And I encourage you to be present, follow along, and pick up the concepts and mental models without trying to read every line of code.
00:10:05.520 For the rest of it, I've got some resources at the end, and I'll invite you to keep an eye on the Thoughtbot blog for write-ups of these examples in more detail.
00:10:12.840 Alright, I'm going to take us through building and then enhancing a site with Hotwire.
00:10:19.380 What site? The Ruby on Rails Guides!
00:10:25.099 I hear you out there! Yeah, I do. Saying: 'Aji, is this a plug for your new podcast, The Tightly Coupled Book Club,' where you and your equally charming and vastly more intelligent wife, Mina, and fellow Rails developer read the Rails Guides cover to cover and discuss it like a book club?
00:10:30.120 First of all, how dare you?! That is a baseless accusation, and that is so obviously true.
00:10:37.680 I am not going to dignify it with a response other than please follow us in your podcaster of choice, rate and review us on iTunes, and buy a Casper mattress.
00:10:43.680 Upon arrival at the guides, we're greeted with links that each open their own page.
00:10:49.080 Therefore, we will refer to these as pages within a page.
00:10:54.540 There are chapters within chapters, and there are paragraphs.
00:10:59.700 And that's where we'll start: with pages that have many chapters and chapters that have many paragraphs.
00:11:05.880 The structure of our exploration will be mostly to the tune of 'if that in React, think this in Hotwire.'
00:11:11.520 I say mostly because the first thing I want to do is immediately not do that at all.
00:11:16.740 There is a mindset difference in approaching assembling a site with Hotwire.
00:11:21.699 It is this: start as if you didn't have Hotwire at all.
00:11:27.360 If we follow Rails conventions, we'll be able to use Turbo to enhance the experience by adding only a few lines of code and little, if any, JavaScript.
00:11:34.200 This was probably the biggest hurdle for me, thinking that the document would be partitioned off and calling for new components, instead of simply requesting a page.
00:11:41.160 Let's start with these three routes, and we'll take a look at the mock-ups.
00:11:48.780 The first is Pages Index; this is going to be the layout that we're working in.
00:11:54.299 A Rails red header up at the top, the pages list on the side, and because the user hasn't chosen a chapter yet, we have nothing to load.
00:12:01.640 So why not go for some Rails nostalgia, and here's Page's show?
00:12:07.320 Same header, chapter placeholder, but the page list is expanded for the selected page, and we can see the chapter titles.
00:12:14.440 And last is Chapters Show.
00:12:21.480 Maybe we saw this coming, but it's going to show the chapter.
00:12:27.480 So I've set up a partial that will help us deliver the three-part layout that we see here.
00:12:32.400 We'll call it Navigation Frame. So, it holds the header, which doesn't really change.
00:12:39.540 And these yield calls here are named to be able to target the specific locations later.
00:12:45.300 If you're thinking about putting this in a layout, hold on to that thought.
00:12:51.120 So we're going to go to the chapter show template to get a feel for where we're going to be spending a lot of our time.
00:12:58.020 These two content four calls matched up with the named yield regions we saw before. They're holding all the content for those portions of the page.
00:13:04.320 And that content is in these two partials, we'll open them up when they become relevant.
00:13:10.980 But let's take a look at what the site looks like from here.
00:13:18.840 Pretty good so far. Navigation frame is holding up the layout, and those partials iterate the content pages and chapters on the left.
00:13:24.480 Chapters plus paragraphs on the right.
00:13:30.420 One of the building blocks of Turbo is the Turbo Frame. It's described pretty often as being scoped navigation.
00:13:36.600 So look at the chapter region of the layout: if that box was a Turbo Frame, interactions inside it would, by default, only affect what's inside the frame.
00:13:42.600 So what is the React equivalent of scoped navigation?
00:13:48.980 In React, scope navigation kind of doesn't make sense. You follow along the rendering path and get to the end.
00:13:54.300 There just isn't a direct link here; navigation is already scoped.
00:14:01.200 It's not the wild west like a web page; navigate wherever the anchor tag takes it. We're hardwired to know the only way to that other component is back up and over.
00:14:08.220 So what even is a frame in this context?
00:14:15.660 From the layout, I see two candidates for Turbo Frames: the left that switches between Pages Index and Pages Show, and the right, which shows a single chapter at a time.
00:14:21.240 We'll stick to the chapters template, but know that we'll need to put this frame in the same place on all three pages.
00:14:27.720 Index Pages Show and here in Chapter Show. We want the Turbo Frame to wrap everything that should update when a link inside itself is clicked.
00:14:34.720 That's the whole chapter region, so we'll make it the topmost element in that block.
00:14:40.440 Rails provides a Turbo Frame tag helper; this renders out to a custom element of, you guessed it, Turbo Frame.
00:14:48.060 And you see there, it makes the first argument an ID.
00:14:53.640 That's actually it! That's all you have to do.
00:14:58.920 What does this look like in the browser?
00:15:05.520 That is a different video! I promise we'll be able to see the difference if we take a look at what HTML is actually being sent over the wire.
00:15:11.520 This is the HTML delivered by the server when we first visit Chapter One.
00:15:17.640 And I certainly do not expect anyone to be able to read this.
00:15:23.040 I'll take you through it: these lines here are the head tag, JavaScript, CSRF meta tags, page title, style sheets... nothing out of the ordinary.
00:15:29.880 The body tag starts with that Rails red header that we've been working with.
00:15:37.440 Here's the page region; there is a selected page with three open chapters currently.
00:15:44.160 And this is that for a selected chapter with all of its paragraphs intact; that is, all of the markup of our page.
00:15:52.620 From Rails's head tag, our layouts, pages, and chapter...
00:15:58.620 And right here at line 66 is our Turbo Frame.
00:16:04.560 Even so far, it’s as expected! But what happens when we click that link from inside the frame?
00:16:10.200 That next chapter link... alright, the second chapter can't be that much shorter, right?
00:16:16.020 So there, this is the chapter, there is the page list, there's that header, and that's the head tag... it's empty!
00:16:23.040 When Turbo intercepts the clicks, it sends the request via JavaScript, including a special header, Turbo Frame in the frame's ID.
00:16:30.840 That way the server knows that the page it's talking to is Turbo enabled. It already has the contents of the head tag and doesn't bother sending it!
00:16:37.200 You know what else it doesn't send? The layout!
00:16:43.920 So, we didn't put the navigation frame code there, and the Turbo Frame can't be there either.
00:16:51.000 And this is why!
00:16:56.640 So, we'll take a little peek into that Hotwire mental model.
00:17:02.880 We make our first request and receive chapters one. Remember this section of the page is encased in a Turbo Frame with the ID 'chapters'?
00:17:09.180 When we click the next chapter link, Turbo sends a request for chapters two, and down it comes.
00:17:15.600 It's essentially the whole page minus the head tag.
00:17:22.200 Turbo is going to inspect this response and look for a Turbo Frame element that matches the one it asked for of chapters.
00:17:27.720 If it finds it, Turbo will punch out the old chapters frame and throw it away.
00:17:34.800 It will remove the new chapter's Turbo Frame, throw away the rest of the response, and switch the new frame in, having only updated the part that needed to change.
00:17:41.520 Sounds kind of Reacting, right? React won't update parts of its component tree and therefore the DOM when data dependencies haven't changed.
00:17:47.640 Neither will Turbo update the DOM if it hasn't been directed to.
00:17:55.020 Alright, let's jump into another example! Let's apply a Turbo Frame to the pages region.
00:18:03.240 Just as we wrapped the chapters region in a Turbo Frame, let's do the same with the pages.
00:18:10.320 Same thing, we're ready to go!
00:18:15.600 It's pretty great to be able to take advantage of this functionality with so little code.
00:18:21.120 So let's see how this improves the experience in the browser.
00:18:29.340 Okay, clearly we have broken something.
00:18:37.080 If we think about the interaction that Turbo is having with the response it should become clear after clicking that link.
00:18:43.920 It's gotten rid of the old Pages frame and replaced it with the new one.
00:18:50.520 But if the left sidebar is scoped to that frame and the chapters were never in that frame, this is a pretty common use case.
00:18:58.680 Right? You click on a link in the header or sidebar of a web page and the main section changes.
00:19:05.520 In React, we find ourselves needing to reach another branch of the tree.
00:19:11.320 We know that it's going to have to go through the closest mutual parent's component.
00:19:17.320 That parent component will send a callback down one side and then send props across and down the other.
00:19:23.880 So, think this in Hotwire.
00:19:29.980 So, we're on the page partial. It's not the show or index template; it's the models partial where we iterate the chapters into links.
00:19:36.960 We can tell this link which frame we want it to swap out by adding a data attribute to the anchor tag.
00:19:43.320 Turbo frame tells Turbo to target the chapters frame from the response instead of the frame that originated this request.
00:19:49.560 Turbo action tells the URL to update to the address of this link; that way, our users can bookmark specific chapters and come right back later.
00:19:56.700 Let's see the chapter's controller.
00:20:02.460 So, we've had this query for page.all sitting here this whole time.
00:20:09.300 If we peek at the top of the chapter show template, we are rendering that page index and it needs the data somehow.
00:20:16.500 Even so, I don't particularly love that this page data is getting mixed up in our chapters controller.
00:20:24.960 And as you might expect, there is a Turbo answer here.
00:20:31.980 We'll start by taking out the offending database query and moving it over to this chapter show template.
00:20:38.280 And we'll get rid of the page list partial; it's why we needed that query, so bye!
00:20:44.520 We'll again add an option; this time, the Turbo Frame source attribute tells the frame to begin Turbo navigating as soon as it's loaded into the DOM.
00:20:50.880 So, in a React component version of what we're trying to accomplish, a useEffect hook makes a network request and places the response in the component state.
00:20:57.360 The empty dependency array tells us it's going to do that just once on mount.
00:21:04.680 And if we don't yet have data for the pages, we'll render a loading message.
00:21:10.560 Once we have it, we'll render the page index.
00:21:17.160 That's a pretty common pattern in the React world: to eager load some data as soon as the component is activated.
00:21:23.040 Display a loading message for the user.
00:21:29.880 So when the page loads, Turbo will notice the URL and fire off that request.
00:21:36.000 But the React version was able to handle a loading state as well, right?
00:21:42.180 Well, the block inside the Turbo Frame will be rendered in the same manner as before.
00:21:48.120 It'll be replaced, though, as soon as the response comes back.
00:21:54.180 So let's take a look at this live.
00:22:01.080 I'm going to artificially slow the server's response so that we can actually see the loading state.
00:22:06.960 Ah, great! Fix it!
00:22:12.240 The most exciting thing about this here for me, though, is that each of the clicks on the chapters on the left are also visiting the chapters show action.
00:22:19.680 And if it were a full page reload, it would hit that slow query all over again.
00:22:25.620 But the navigation is contained to our Turbo Frame on the right. Everything stays snappy!
00:22:31.260 Look here at the newly revamped Rails Guides: we want to make contributing to Rails easier than ever before.
00:22:38.160 So in that spirit, we are announcing live inline editing of every paragraph on the guides.
00:22:45.600 Because there's no way that this could go poorly!
00:22:52.920 I feel like this is a wheel I've either reinvented or seen reinvented several times across the React products that I've worked on.
00:22:58.680 We need to be able to toggle between read mode and edit mode in line, and have live updates on save.
00:23:05.520 Given the ease of conditional rendering in React, having a component that toggles between two children is actually pretty straightforward.
00:23:13.680 We'll need some local state to handle the state of the conditional.
00:23:19.920 We can pass into components one for each view and edit, and each of them can handle their own manner of viewing, editing, and persisting however they like.
00:23:26.520 The view-edit wrapper can very easily be separate from any of that logic.
00:23:31.680 It's even pretty trivial to be resource-agnostic from this component's point of view.
00:23:38.160 And we send down a callback to handle the toggle for each of the components to implement however they see fit: button, keyboard shortcut, timeout, whatever!
00:23:44.280 So we'll start by looking at the paragraph controller.
00:23:50.340 This is a standard off-the-shelf Rails controller that very well could have come with Rails G scaffold.
00:23:56.220 It only has edit and update because we're going to leverage some of our other infrastructure otherwise.
00:24:03.660 And we can see some of that at play here.
00:24:10.680 We won't get into some of the nifty things Turbo can do with forms today, but instead we're going to render the paragraphs chapter.
00:24:17.880 Which contains all its markup.
00:24:24.360 I've taken the liberty of adding the necessary routes as well.
00:24:31.680 So we'll visit our chapters partial.
00:24:37.320 This is the only place that we've been rendering the paragraph so far, right here.
00:24:44.160 The content is going straight into the CramDown gem to take the plain text from our database and turn it into markup in HTML.
00:24:50.640 Let's extract a component - I mean partial - for our paragraphs.
00:24:57.000 Okay, we're off to the races! Take a look at that.
00:25:00.540 Cool! So this is really all that we've ever had for the paragraph so far.
00:25:09.480 But we're going to start out with our new friend and wrap this in a Turbo Frame.
00:25:16.380 We've got a little something new this time around, though: DOM ID is a helper method that Rails gives us.
00:25:22.200 To make identifiers from objects, it matches up their name and their database ID as well as optional arguments.
00:25:28.020 It's a shortcut for creating a meaningful ID attribute for your Turbo Frames.
00:25:35.160 If this one was passed a paragraph with ID 1, it would generate this... easy peasy.
00:25:41.880 Some quick styling considerations, and here we are.
00:25:48.300 A link to the RESTful edit route associated with this resource, a Rails linked to Rails URL helper.
00:25:55.380 The closer we stick to convention, the easier this is going to be to fit together.
00:26:01.920 This is the most specific and kind of tightly scoped Turbo Frame that we've tried yet.
00:26:08.100 Let's look at the edit template.
00:26:16.020 To blank slate so far. It's almost a shame to fill it with the least surprising form possible.
00:26:22.080 Oh well! Can't forget the Turbo Frame tag.
00:26:27.120 Remember, we've got that narrowly focused one around the link that gets us here!
00:26:34.560 So let's take a look.
00:26:40.800 Nice! So look at that frame, scoped to an instant specific identifier, can be mighty powerful indeed.
00:26:47.520 And we've done our part to ruin the brand new Rails Guides.
00:26:54.000 There is one thing to point out: we did it because of limited time and space, but our other view templates (pages index, chapters show, etc.) had all of the places for putting together the entire page.
00:27:00.840 The paragraph edit did not. It did have the Turbo Frame that we needed.
00:27:06.300 So its limited markup did the job, but what would that route look like without the rest of the site already loaded and Turbo spun up?
00:27:13.080 Not the best UX, is it?
00:27:17.640 Alright, so keep that in mind as you're building out these sites. Alright, let's do something fun.
00:27:30.720 I mean, it's all been fun, but funner!
00:27:35.640 We've been focusing so much on Turbo we haven't talked at all about Stimulus.
00:27:41.040 This is Hotwire's client-side JavaScript component.
00:27:48.240 So often, Hotwire opens possibilities without more JavaScript, but there are some behaviors that have to take place completely on the client side, like keyboard shortcuts.
00:27:54.540 We're going to add a modal chapter search that can be triggered by hockey.
00:28:02.820 Keeping in mind React's power when it comes to conditional rendering based on a Boolean switch, let's focus on the keyboard shortcut.
00:28:09.840 Go!
00:28:11.880 We've got to useEffect that starts us off by setting some vanilla JavaScript event listeners.
00:28:16.440 And we'll be waiting to hear a keydown event.
00:28:24.180 We'll return a cleanup function to make sure the listener is cleared when this component unmounts.
00:28:30.240 Here's the keydown callback: we'll inspect the event for the key combinations we care about and update the state!
00:28:37.560 Now we are going to open up the layouts application template.
00:28:44.700 Since our attributes that define the interaction with key events are application-wide, we'll put it on a highest component straight on the body tag.
00:28:50.040 Stimulus connects to the environment through the DOM via these attributes.
00:28:55.200 The first one we'll add is the name of the controller we want to connect to.
00:29:02.520 This element data-action defines the event handlers, and the keydown event is filterable by key press.
00:29:08.880 So these two lines here are actually all that's needed to set both MetaKey and Escape hotkey handlers.
00:29:15.720 The right side of the arrow in these expressions denotes the controller and function we want those events to trigger.
00:29:23.040 The same controller hash method convention that you see in the routes file or Ruby API docs.
00:29:30.060 Now following convention over configuration, and true Rails style, the file name matches to the data controller value in the body tag.
00:29:37.260 Making this controller wake up and pay attention.
00:29:43.680 The toggle function manages a CSS class on the modal, so we'll hide it or show it.
00:29:50.940 And these target definitions, those arrays of strings, map behaviors to elements on the page that we'll identify in the HTML.
00:29:56.820 Stimulus does the DOM scanning for us.
00:30:02.700 Back to the navigation frame!
00:30:09.060 So half of this file that we've seen before. I'll get a little closer to the new stuff.
00:30:15.660 The modal, starting hidden, wraps a Rails form with a single text input and a few Stimulus-specific data attributes.
00:30:20.880 Those are the attributes that identify the elements to the controller for use in the functions that we saw.
00:30:26.880 Those targets: data-controller name and target string.
00:30:31.680 It'll submit with a GET request to a new route, and the response will be scoped to an empty Turbo Frame.
00:30:38.880 That is the target of our form. Without it, the response would have no frame to replace into, and it would take over the entire page.
00:30:44.520 So, in the chapter search controller...
00:30:49.560 We're going to perform a fairly naive search through an SQL-like wildcard implementation.
00:30:56.420 Which, in turn, renders this view.
00:31:01.920 Which iterates the matched chapters, linking to each.
00:31:10.440 And if we look here, there is a target option with the name underscore top.
00:31:15.360 This is new! We don't want the resulting links to try and be scoped into the search results frame.
00:31:21.300 Instead, underscore top tells the page to perform a full navigation to the show path of the chapter in the link.
00:31:29.880 Let's see what we've got!
00:31:34.140 There we go! Cool! That's actually the last example that I could squeeze into this talk.
00:31:40.740 So I know it's a little abrupt, but thank you all!