---
title: 'My introduction to Superglue: React ❤️ Rails'
teaser: Join me on my first journey with this new library.
tags: react,rails,javascript,web
author: Dave Iverson
published_on: 2023-04-19
---

I just joined a new project building a Ruby on Rails app with some React bits. The React bits are implemented with [Superglue](https://thoughtbot.github.io/superglue/#/). This was my first experience with the Superglue library, and these are my first impressions.

First, some context:

I used to build Rails apps often. Recently I’ve been writing JavaScript/React apps. I skimmed the Superglue docs, but I don’t know much about it. It seems to take some inspiration from Turbolinks. I have an aversion to Turbolinks from the old days.

The selling point of Superglue is that it makes it easier to get Rails data into a React app. I’ve often worked on projects where React calls a Rails API backend via AJAX requests. Maintaining this link is annoying: you must set up CORS, implement security, and define an API schema. Let's find out if Superglue can simplify it.

thoughtbotter [Johny Ho](https://github.com/thoughtbot/superglue/graphs/contributors) built Superglue and championed its use on this project.

So far with Superglue, I’ve:

- edited an existing React component on a Superglue screen
- made some AJAX requests to get dynamic data
- created a new Superglue screen

## The structure of a Superglue screen

The first thing I did on this project was to figure out what files are involved with Superglue. Our app has the concept of Superglue screens: entire screens built with React instead of Rails ERB templates.

Each Superglue screen has 3 files:

- an `index.html.erb` template that sets up the initial state of the app (it’s pretty sparse)

```erb
<% initial_state = controller.render_to_string(@virtual_path ,formats: [:json], locals: local_assigns, layout: true) %>

<script type="text/javascript">
  window.SUPERGLUE_INITIAL_PAGE_STATE=<%= initial_state.html_safe %>;
</script>
```

- an `index.jsx` file that defines the page’s React component

```javascript
export default function DashboardIndex(props) {
  const {flash, alert} = props;
  return <div>
    <Flash flash={flash} />
    {alert && <Alert {...alert} />}
    Welcome to the dashboard!
  </div>
}
```

- an `index.json.props` file that declares the props for the React component to receive

```ruby
if current_user
  json.first_name current_user.profile.first_name
  json.alert Alerter.for_dashboard(current_user, self)
else
  json.first_name "Guest"
end
```

## Editing a Superglue React component

Modifying an existing component was easy. It’s just writing JSX. Fortunately, this project is already configured with Webpacker. I didn’t have to fight the build system. To make UI changes, I wrote them to the JSX file, saved it, and reloaded the webpage!

Editing the props for an existing component was also easy. But I did have to learn how props work. The `index.json.props` file uses JBuilder, a Ruby DSL, for building JSON. Ruby sure does like DSLs. It’s not hard to work with, especially when I figured out how to make my editor use Ruby syntax highlighting.

I don’t know when logic and helper methods should go in the `index.json.props` file vs. in the controller. I declared a helper function and did some calculations in the JSON props file. Maybe that should have happened in the controller? Presumably, this props file works like a view and can access any instance variables in the controller’s scope.

The cool thing about this JSON props file is that I can preview it! Loading `http://localhost:3000/dashboard.json` returns the contents of the JSON. The React component will get all this data as props.

Here’s what the generated JSON looks like:

```json
{
  "data":{
    "firstName":"Dave",
    "alert":{
      "type":"warning",
      "title":"Your bank account balance is negative.",
      "body":"How do I get back to healthy account balance?"
    }
  },
  "componentIdentifier":"dashboard/index",
  "defers":[],
  "fragments":[],
  "assets":[
    "/packs/js/application-da6da53e60e0483f9c77.js",
    "/assets/application-3e41d1709623783400ffae3905ec7116cc906199c9fa52475a2dce6cd9c30fab.css"
  ],
  "csrfToken":"kRpOJQ4UjuhN9hqXClMxz7Cs60Yp1PgG1KmjeCpTh68fEzwdeI6dyThL50Ko5WwKBz528qRvk4GrVXdMpvX50Q",
  "restoreStrategy":"fromCacheAndRevisitInBackground",
  "renderedAt":1678900171,
  "flash":{}
}
```

I don’t know what’s up with all that metadata at the end. What does Superglue do with it? Let’s ignore those JSON fields for now.

I also discovered that I can pass URL parameters to this `dashboard` endpoint. I can use that to preview how it works with different arguments.

## Adding AJAX to a Superglue component

Next, I needed to try out the AJAX features of Superglue. My new React screen has a set of tabs. Each tab shows different data in a chart. Superglue can initialize a React component with the first tab’s data. I think I can make it dynamically load another tab’s data when you click that tab.

This bit was quick and straightforward, but I still don’t understand how it works. I found an example to copy from another file. In the props file, it’s easy to generate different data. We’ll receive a URL parameter (`params[:tab_name]`), and we can use it to return different props data:

```ruby
if params[:tab_name] == "month"
  json.data monthly_chart_data
else
  json.data weekly_chart_data
end
```

We can also return some information about the tab so that React knows whether to highlight it and what URL it should use to request updated props.

```ruby
json.month_tab do
  json.path send(index_path, {
    cashflow_interval: :month,
    props_at: props_at
  })
  json.is_active params[:tab_name] == "month"
end
```

I don’t understand what that `props_at` URL parameter is doing, why we need it, or even where the variable comes from.

Here’s how I built the tabs in React:

```jsx
const { weekTab, monthTab } = props;
...
<div className="tab__container">
  <a
    href={weekTab.path}
    aria-label="Weekly"
    data-sg-remote={true}
    className={`tab button ${
      weekTab.isActive && 'active'
    }`}
  >
    Week
</a>
  <a
    href={monthTab.path}
    aria-label="Monthly"
    data-sg-remote={true}
    className={`tab button ${
      monthTab.isActive && 'active’
    }`}
  >
    Month
</a>
</div>
```

That `data-sg-remote` data attribute must tell Superglue to make an AJAX call and rerender this React component with the new props. And we can check the `monthTab.isActive` prop to figure out if that tab is currently active.

## Building a new Superglue screen

It was not hard to make a new screen. A little confusing. First, I made the 3 files (`onboarding/index.jsx`, `onboarding/index.html.erb`, and `onboarding/index.json.props`). In the ERB file, I copied the boilerplate `window.SUPERGLUE_INITIAL_PAGE_STATE ` from a previous screen.

Now that the ERB file was done, I could build a React component - the easy part for a React dev. But when I loaded the screen, it was blank, and there was an error in the console:

> Uncaught Error: Superglue Nav component was looking for onboarding/index but could not find it in your mapping.

I guess new React files aren’t hooked up automatically. I found the mapping file in `packs/application.js`. This file seems to be where Superglue (and Redux) are initialized. I found  the spot to add the new mapping:

```javascript
const identifierToComponentMapping = {
  'dashboard/index': DashboardIndex,
	// NEW:
  'onboarding/index': OnboardingIndex,
}
```

But how does Superglue figure out which path to use? It’s not a URL path (`/onboarding`). Might it be a reference to the controller and action? 🤷 Whatever, it works now. My component appears on the screen.

I also tried writing some HTML in the `onboarding/index.html.erb` template file to see if it would appear alongside the React app. But it didn’t appear on the page. Frustrating. I found that our Superglue layout file (`views/layouts/superglue.html.erb` has a `<%= yield %>` inside the React div.

```html
<!DOCTYPE html>
<html lang="en">
  <%= render "application_head" %>
  <body>
    <div class="application-container">
      <%= render "application_nav" %>
      <main>
        <div id="app" class="application-inner-container">
          <%= yield %>
        </div>
        <%= render "javascript" %>
      </main>
    </div>
  </body>
</html>
```

As a result, any HTML acts as no-js content - it gets replaced by the React app. That’s fine, but I might move the `yield` outside that div. Then I can build a hybrid screen: half React and half ERB. It would be especially nice if I could inject ERB template content INSIDE the React app. But I don’t think it’s possible.

Every controller and view can decide whether to render ERB or Superglue React. The controller chooses which layout to use: `render layout: "superglue"` or `render layout: "application"`.

The final step is to hook up some props for the new screen. I edited the new `onboarding/index.json.props` file. (Why is this called `.json.props` instead of `.props.json` or `.json.rb`? I don’t know.)

I added a quick prop:

```ruby
json.first_name "Guest"
```

and confirmed that my component received it. Just like magic!

## General thoughts about Superglue

There’s a lot of magic going on that I don’t quite understand. Then again, this is Rails, and magic is par for the course.

Here are some of the things I still don’t get:

- what’s Redux doing, and do I need to care?
- what’s the `props_at` thing I see in the props files?
- how does Superglue know which JS file to load?
- can I render ERB code inside a React component?
- how does `data-sg-remote` work?
- why are the Jbuilder variable names snake_cased, but the JS props are camelCased?

I should read the docs.

I wish Superglue’s AJAX calls were more intuitive. I doubt I could build something complicated with that little `data-sg-remote` attribute.

I still haven’t learned how a POST request would work with Superglue. Can you submit a form and have Rails perform the validation and return JSON error responses?

I wish Superglue supported an “islands” architecture. I’d love to build screens with a few mini React components sprinkled inside an ERB template. But it seems to only support one React component per page.

What I appreciate the most about Superglue is that I don’t need to write an API and AJAX requests for React to talk to Rails. I love that I can just put a Rails variable in the JSON props file, and React will automatically receive it. This alone makes Superglue valuable to me.

So the big question is: would I use Superglue on a new project? Maybe. I’d use Superglue if:

- I only plan to have a few React screens
- I don’t need to use a React client-side router
- The React stuff is read-only (no forms)
  - I might change my mind when I learn more about this
- Installing Superglue in a new Rails app isn’t too hard

As I keep working with this framework and learning its patterns, I'm growing more attached to it. Superglue is still young, and I expect it will become more approachable with time. I’m excited that folks like Johny are working on ways to keep Rails relevant in a React-heavy world.

## Next Up

I've shared this post with Superglue creator Johny and he’s writing a response. He has some corrections, clarifications, and a peek under the hood. Look for that blog post soon!
