At thoughtbot, we rely on Rails to build applications at high velocities. When faced with adding modern client-side interactivity, we may have many forms of progressive enhancement to turn to. In particular, gaining popularity within the team are the trio of companion libraries: Hotwire, Turbo, Stimulus. These frameworks make it easy to add sprinkles of interactivity to HTML and have been a much easier option to reach for than React, Redux, and friends.
But React is so tempting!
Components make organizing testable units of interactivity a breeze. Being declarative, it’s a lot easier and sensible to manipulate state as a simple data structure than to carefully manipulate the DOM. And while Hotwire and friends excel at quickly building light to medium complexity, React excels at that and beyond.
Why can’t we have it both ways?
Introducing Superglue. A framework that makes building Rails, React, and Redux applications as productive as Rails, Hotwire, Turbo, and Stimulus applications. It brings a developer experience that is as much classic Rails as it is normal React and Redux.
It’s Rails
Superglue leans on Rails’ ability to respond to different mime types on the
same route and divides the usual foobar.html.erb
into three familiar
templates.
foobar.json.props
A presenter written in a jbuilder-like template that builds your page props.foobar.js
Your page component that receives the props from above.foobar.html.erb
Injects your page props into Redux when the browser loads it.
Shape your props
to roughly how your components are presented. For example:
json.header do
json.username @user.username
json.linkToProfile url_for(@user)
end
json.rightDrawer do
json.cart(partial: 'cart') do
end
json.dailySpecials(partial: 'specials') do
end
end
json.body do
json.productFilter do
form_props(url: "/", method: "GET") do |f|
f.select(:category, ["lifestyle", "programming", "spiritual"])
f.submit
end
end
json.products do
json.array! @products do |product|
json.title product.title
json.urlToProduct url_for(product)
end
end
end
json.footer do
json.copyrightYear "2023"
end
Familiar Rails conveniences include form props, a fork of form_with
made for
React; the flash is integrated as a Redux slice; and Unobtrusive Javascript helpers.
It’s React
But there are no APIs! The above is injected as a script tag in the DOM so everything
loads in the initial request. Its added to your Redux state and passed to
foobar.js
as props, for example:
import React from 'react'
import { useSelector } from 'react-redux'
import { Drawer, Header, Footer, ProductList, ProductFilter } from './components'
export default function FooBar({
header,
products = [],
productFilter,
rightDrawer,
footer
}) {
const flash = useSelector((state) => state.flash)
return (
<>
<p id="notice">{flash && flash.notice}</p>
<Header {...header}>
<Drawer {...rightDrawer} />
</Header>
<ProductList {...products}>
<ProductFilter {...productFilter} />
</ProductList>
<Footer {...footer} />
</>
)
}
It’s Turbolinks and UJS
At heart, Superglue is a fork of Turbolinks 3, but instead of sending your
foobar.html.erb
over the wire and swapping the <body>
, it sends
foobar.json.props
over the wire to your React and Redux app and swaps the
page component.
This behavior is opt-in. Superglue provides UJS helpers that you can use with your React components to SPA transition to the next page.
<a href=”/next_page” data-sg-visit> Next Page </a>
It’s more!
Being able to easily use React in place of ERB isn’t enough. Superglue’s secret
sauce is that your foobar.json.props
is diggable; making any part of your page
dynamic by using a query string. It’s a simpler approach to Turbo Frames and
Turbo Stream.
Need to reload a part of the page? Just add a query parameter and combine with
the UJS helper attribute data-sg-remote
:
<Header {...header}>
<Drawer {...rightDrawer} />
<a data-sg-remote href='/some_current_page?props_at=data.rightDrawer.dailySpecials'>
Reload Daily Specials
</a>
</Header>
The above will traverse foobar.json.props
, grab dailySpecials
while
skipping other nodes, and immutably graft it to your Redux store.
This works well for modals, chat, streaming, and more! All deserving a blog post of its own.
Should I use Hotwire or Superglue?
With Superglue you have a new, yet familiar option for building apps rapidly. Superglue does require maintaining three separate templates instead of one, but it makes building medium to complex functionality comparatively easier than Hotwire and friends. A non-trivial application built in both Stimulus, and Superglue is available if you’d like to compare and contrast the two approaches.
For some, Hotwire and friends is enough, but for those who believe that components and modern interactity is React and Redux’s strength, I encourage you give Superglue a try.