---
title: 'Introducing Superglue: React ❤️ Rails'
teaser: We've built Superglue, a framework that makes Rails and React as productive
  as Rails, Hotwire, Turbo, and Stimulus.
tags: ruby,web,ruby on rails,react,redux,javascript
author: Johny Ho
published_on: 2024-01-04
---

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:

```ruby
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] (UJS) 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:

```js
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.

```jsx
<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`:

```jsx
<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.

[Superglue]: https://thoughtbot.github.io/superglue
[Stimulus]: https://github.com/seanpdoyle/select-your-own-seat
[and Superglue]: https://github.com/thoughtbot/select-your-own-seat-superglue
[modals]: https://thoughtbot.github.io/superglue/#/recipes/modals
[Turbolinks 3]: https://github.com/turbolinks/turbolinks
[more]: https://thoughtbot.github.io/superglue/#/
[your redux state]: https://thoughtbot.github.io/superglue/#/redux-state-shape
[redux slice]: https://thoughtbot.github.io/superglue/#/page-response?id=slices
[flash]: https://thoughtbot.github.io/superglue/#/rails?id=rails-flash
[UJS helper]: https://thoughtbot.github.io/superglue/#/./react-redux?id=action-creators
[form props]: https://github.com/thoughtbot/form_props
[Unobtrusive Javascript]: https://guides.rubyonrails.org/working_with_javascript_in_rails.html#replacements-for-rails-ujs-functionality
