Slide 1

Slide 1 text

Hi, I'm Julian and I'm on the StimulusReflex core team. ( ! ) Twitter: @julian_rubisch Github: @julianrubisch / @stimulusreflex Discord: https://discord.gg/stimulus-reflex 1

Slide 2

Slide 2 text

Here are some of the questions I get asked most frequently: → "Should I use Hotwire or StimulusReflex?" 2

Slide 3

Slide 3 text

Here are some of the questions I get asked most frequently: → "Should I use Hotwire or StimulusReflex?" → "Will Hotwire kill StimulusReflex?" 3

Slide 4

Slide 4 text

Here are some of the questions I get asked most frequently: → "Should I use Hotwire or StimulusReflex?" → "Will Hotwire kill StimulusReflex?" → "Is Hotwire the StimulusReflex successor?" 4

Slide 5

Slide 5 text

Here are some of the questions I get asked most frequently: → "Should I use Hotwire or StimulusReflex?" → "Will Hotwire kill StimulusReflex?" → "Is Hotwire the StimulusReflex successor?" → "Turbo to replace StimulusReflex?" 5

Slide 6

Slide 6 text

Here are some of the questions I get asked most frequently: → "Should I use Hotwire or StimulusReflex?" → "Will Hotwire kill StimulusReflex?" → "Is Hotwire the StimulusReflex successor?" → "Turbo to replace StimulusReflex?" 6

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

First, some definitions Hotwire is an "umbrella" brand of the new Basecamp/Rails frontend stack → Turbo → Stimulus → Strada (?) 8

Slide 9

Slide 9 text

First, some definitions Turbo 1. Turbo Drive formerly known as Turbolinks, intercepts link navigation 2. Turbo Frames decomposed pages, scope navigation 3. Turbo Streams deliver page changes over WS, SSE, respond to form submissions 9

Slide 10

Slide 10 text

First, some definitions Turbo Drive 1. Application Visits → advance (history.pushState) → replace (history.replaceState) 2. Restoration Visits → restores from Turbo cache (incl. scroll position) → ⬅ or ➡ browser buttons Opt out: data-turbo="false" 10

Slide 11

Slide 11 text

First, some definitions Turbo Frames → Decompose the page into parts to be updated on request. → Links or forms are captured and automatically updated (regardless if full document or fragment) 11

Slide 12

Slide 12 text

First, some definitions Turbo Frames 12

Slide 13

Slide 13 text

First, some definitions Turbo Frames 13

Slide 14

Slide 14 text

First, some definitions Turbo Frames

Track 2

14

Slide 15

Slide 15 text

First, some definitions Turbo Frames 15

Slide 16

Slide 16 text

First, some definitions Turbo Streams Turbo Streams deliver page changes as fragments of HTML wrapped in self-executing elements. → target ID + action, usually via WebSockets → targets can also be CSS selectors (.messages etc.)
16

Slide 17

Slide 17 text

First, some definitions Turbo Streams Available Actions: → append (to a list) → prepend (before a list) → (insert) before → (insert) after → replace → update → remove 17

Slide 18

Slide 18 text

First, some definitions Turbo Streams Responding to Form Submissions def destroy @track = Track.find(params[:id]) @track.destroy respond_to do |format| format.turbo_stream { render turbo_stream: turbo_stream.remove(@track) } end end 18

Slide 19

Slide 19 text

First, some definitions StimulusReflex → a library exclusively for Reactive Rails, somewhat inspired by Phoenix LiveView → based on CableReady (the "missing ActionCable standard library"1) → initially by Nathan Hopkins (@hopsoft), now maintained by a 4- headed core team → Demo 1 Yours Truly 19

Slide 20

Slide 20 text

More Qualifications 1. I'm biased ! 2. Still, I try to follow the Rails Golden Path as far as possible 3. In my own apps, I use < 10% Reflexes What follows is an assortment of juxtapositions, distilled into my (very subjective) Best Practices/Heuristics 20

Slide 21

Slide 21 text

Not a Best Practice 21

Slide 22

Slide 22 text

Direction of Concern Turbo "Resource-oriented" StimulusReflex "Aspect-oriented" 22

Slide 23

Slide 23 text

Paradigm Turbo → REST → Request ➡ Response → State Manipulation follows strict REST principles (form submits) StimulusReflex → RPC → Full-Duplex Persistent Websocket Connection → Any valid JS Event Emitter can initiate State Manipulation (, , , custom elements/ web components, ...) 23

Slide 24

Slide 24 text

Transport Turbo → HTTP (Drive, Frames) → WS (Streams) StimulusReflex → WS 24

Slide 25

Slide 25 text

State Turbo → Stateless StimulusReflex → Stateful 25

Slide 26

Slide 26 text

Reactivity Turbo → 7 Turbo Stream operations → Lifecycle: → turbo:before-stream-render → turbo:frame-render → turbo:frame-load StimulusReflex → morphdom + 33 operations via CableReady → Lifecycle: → Server-Side before_, after_, around_reflex callbacks → Client-Side before, success, error, halted, (after), finalize callacks → Generic and Custom client-side lifecycle methods (e.g. Poke beforePoke(element) {}, etc.) 26

Slide 27

Slide 27 text

Scope Turbo → Turbo Frames (referenced by IDs) ! → stateless, behaviorless quasi-"components" → Turbo Stream targets StimulusReflex → Page Morphs (optional data- reflex-root) → Selector Morphs (bypass conventional rendering completely, morph a single HTML element) → Nothing Morphs ( ) 27

Slide 28

Slide 28 text

Demos - StimulusReflex → Filterable → Nested Forms → Composable UI → Dropzone → Wizards 28

Slide 29

Slide 29 text

Refactoring Example - A Music Playlist 1. First Pass - Tabbed Navigation ! Turbo Frames! <% active = session["list_#{@list.id}"][:active] %> <%= turbo_frame_tag "list_entries" %> ... <% end %> <%= turbo_frame_tag "active_track", src: track_url(active) %> 29

Slide 30

Slide 30 text

Refactoring Example - A Music Playlist 2. Second Pass - Session Management class TracksController < ApplicationController def show @track = Track.find(params[:id]) session["list_#{@track.list.id}"][:active] = @track end end 30

Slide 31

Slide 31 text

Refactoring Example - A Music Playlist 2. Second Pass - Session Management class TracksController < ApplicationController def show @track = Track.find(params[:id]) session["list_#{@track.list.id}"][:active] = @track end end ❌ This is a REST violation, we're mutating state on GET instead of POST. 31

Slide 32

Slide 32 text

Refactoring Example - A Music Playlist 3. Third Pass - Hidden Form & Turbo Streams <%= form_with(model: @list, url: active_track_list_path(@list), id: dom_id(@list, :active_track)) do |f| %> <%= f.hidden_field :active_track_id, {value: active.id} %> <% end %> <%= turbo_frame_tag "active_track", src: track_url(active) if active.present? %> 32

Slide 33

Slide 33 text

Refactoring Example - A Music Playlist 3. Third Pass - Hidden Form & Turbo Streams class ListsController < ApplicationController # PATCH def active_track session["list_#{@list.id}"][:active] = Track.find(list_params[:active_track_id]).list_entry.to_gid.to_s render turbo_stream: turbo_stream.replace(@list) end private def list_params params.require(:list).permit(:active_track_id) end end Problem: There's still (a lot of) flicker ⚡ 33

Slide 34

Slide 34 text

Refactoring Example - A Music Playlist 4. Preferred Approach - Update Just a Data Attribute of a Stimulus Controller
...
...
❌ Currently not doable with streams ! Possible remedy: Stream a fragment containing a separate stimulus controller updating just the data attributes 34

Slide 35

Slide 35 text

Refactoring Example - A Music Playlist 5. Here's an Equivalent Reflex <% active = session["list_#{@list.id}"][:active] %> Track 2 # Reflex class ActiveTrackReflex < ApplicationReflex def set_active_track track = element.signed[:track_sgid] session["list_#{track.list}"][:active] = track end end 35

Slide 36

Slide 36 text

Conclusions → Use Turbo to prepare application state, use SR to act on it → Use Turbo for everything that's covered by an HTTP verb (navigation, forms), else use SR → Docs! (DHH: The code == the docs ) 36

Slide 37

Slide 37 text

Resources → David Colby's Blog → Just Enough Hotwire for Rails Developers → My Blog → SR Docs → CR Docs → Discord 37

Slide 38

Slide 38 text

Thanks! 38