Superglue: digging into the framework details

Johny Ho and Dave Iverson

I previously wrote about my experience with the new Superglue framework for building React on Rails.

Now I’ve asked thoughtbotter and Superglue creator Johny to teach me more about how it works.

Adding new Superglue screens

Dave: Superglue doesn’t hook up new React files automatically. I see there’s a mapping file in packs/application.js. This file seems to be where Superglue (and Redux) are initialized. How does it work?

Yes. Files are not hooked up automatically. There’s less magic than you’d expect here. When you load a URL like /posts, there’s an indicator in the layout that tells which component to look for in the mapping.

json.component_identifier local_assigns[:virtual_path_of_template]

which is the same as

json.component_identifier virtual_path

To make the mapping work, we would have to do exactly what you did, insert a new line telling which component to use based on the mapping that you see when you view /posts.json

Initializing page state

Dave: I see that React components receive an initial state from the Rails view. I’m not familiar with render_to_string(). What does this do?

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

This is a Rails thing. If you take an index.html.erb file and call render_to_string(), it returns the HTML string that’s normally sent to the browser. It uses local variables from the controller to render the ERB file.

In this case we’re generating JSON from the .json.props file, not HTML. We put this JSON in a script tag and use it to set up the initial state of the React app, so the screen doesn’t need to make an HTTP request when it loads.

JSON props file naming

Dave: I’m a little confused by the file extensions on props files. Why is it dashboard/index.json.props instead of .props.json or .json.rb?

Ah! That’s because its a mimetype. The Rails signature for handling template responses look like this: `[templatename].[mime_type].[handler]. So an index action callsindex.html.erbwhere the name of the template isindex, the response type ishtml, and we're usingerb` as our template handler.

You may recognize other signatures like index.html.haml, index.csv.erb, index.json.jbuilder, or even index.html for raw html without erb.

In my opinion, The Rails Way > HTML. We often associate HTML with Rails, but did you know Rails responds incredibly well to .xhtml (i’m cheating here), .csv, .xlsx, .js, .json? It’s just a lot of tooling in Rails was built for HTML.

Superglue’s props_template adds more tooling, more conveniences, more excitement for .json and considers another consumer other than the browser - React.

Why is this so cool? How about instead of React, we use React Native? How about instead of index.js, we used index.js.react-native?

JBuilder and props_template

Dave: How do props files like index.json.props work? Do they use JBuilder to build JSON?

Not anymore! props_template is a complete rewrite, not even a fork of JBuilder. But it uses the same syntax. It’s faster than JBuilder because it uses OJ directly, instead of building a Ruby object and calling OJ. (OJ is the preferred Rails JSON serializer.) props_template started as a fork of JBuilder, but that didn’t work because JBuilder doesn’t support layouts (like the superglue.props.json layout). So we needed something new.

Rails has a lot of focus on HTML. But it could use more focus on JSON (and JSON trees). That’s what props_template is solving.

Its responsibiilty is rendering JSON. In the case of Superglue, React is the consumer of the JSON. But React Native could comsume it just as easily.

propstemplate thinks of JSON as a tree and we use `propsat` at to dig into it/traverse it. JBuilder thinks of JSON as a document - a single payload: everything at once.

Because props_template uses trees, you can do fancy stuff like telling it to skip over certain nodes.

Controllers vs props files

Dave: 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?

The props file has access to everything that the ERB file has, including helpers and controller instance variables. Superglue’s props_template is just another view. Helper methods would go in a view helper, just like with ERB views.

However props_template has a feature called “deferment” - it can choose when to render a node. Because of that, it’s often best to put the logic inside a node in the props file. This goes against traditional thinking. But props_template is usually pretty concise - usually less than 100 lines long. So it’s ok to put your logic in the JSON view.

Think about JSON props file as a presenter (from the model-view-presenter pattern). It retrieves data from repositories (the model), and formats it for display in the view. It’s essentially a super-helper class instead of a helper function. The JSON props file is a presenter - it generates an object that gets sent to the view layer (React).

Rail’s ActiveModelSerializers is clunky because it tries to work with models, pulling them from the database over REST. With props_template, we’re representing entire pages (with headers and footers), not models. And it lets us think about this JSON as just another mimetype.

More about props_at

Dave: I see a props_at URL parameter in network requests from React to Rails. What’s it used for?

Try this url: http://localhost:3000/posts/1.json, then this one http://localhost:3000/posts/1.json?props_at=data.comments. Compare the difference. Noticing anything interesting?

localhost:3000/posts/1.json:

{
  "data":{
    "title":"My first post",
    "body":"hello world",
    "comments":{
      "totalPages":30,
      "all":[
        "...all comments.."
      ]
    }
  }
}

localhost:3000/posts/1.json?props_at=data.comments:

{
  "data": [...all comments..]
}

props_at is used to select which JSON node we want to return. You can use it to drill into the JSON, limiting the response to a nested node.

Now put a sleep 10 seconds in posts/show.json.props like this:

sleep 10

json.comments do
  ...
end

Then go to localhost:3000/posts/1.json, it sleeps 10 seconds. Now try localhost:3000/posts/1.json?props_at=data.comments. How does that feel? It doesn’t sleep because only the code in the deeply nested node is run.

Now imagine building an application (chat, infinite scroll, graphs) where you want one part of the screen to reload. Superglue can fetch only what you need.

Superglue metadata

Dave: Can you explain what’s up with all that metadata at the end of the JSON. What does Superglue do with it?

componentIdentifier

We need a way to say that this payload belongs to this particular React component. For example: componentIdentifier: "dashboard/index". Superglue checks the component mapping in application.js, and that’s how it figures how which React page component to render for this route, which props should be passed to the component.

You could theoretically change the componentIdentifier value in the JSON to render completely different screens.

assets

This is the fingerprint of the JavaScript and CSS on the page. If it changes, Superglue will reload the page. Let’s say the user is using the app while you deploy a new version to production. If the user tries to hit a Superglue route, Superglue will detect that there’s a new version available, and will refresh the page. Turbolinks does this too - that’s where we got the inspiration.

defers

We can use defer in the JSON props file to defer execution of that JSON node.

Here’s an example use case: the page needs to run an expensive query and display the results. You can use defer to skip that node when rendering the page, skipping the expensive query (React receives a placeholder value instead - perhaps an empty array or a null.

After the page has loaded, React will automatically make an HTTP request to get the contents of that node. This helps us build websites that load quickly.

fragments

Think of a fragment like a processed partial (enabled using partial: in your props_template). Say you have 5 pages in Redux and they share a header. If you optimistically update 1 header on a single page in Redux, the other 4 pages would also update.

Theres a lot more detail in the documentation.

restoreStrategy

restoreStrategy is what to do when the back button is clicked. You can immediately load what’s in Redux, or wait until you get something back on the server, or a combination of both.

Redux

Dave: What’s Redux doing, and do I need to care?

You can get started without Redux, but it’s valuable to know about it and what it does. When you start to think about optimistic updates, that’s when it become handy.

In short, we just save the contents of the JSON page to it, so I’m using it as an exposed cache. For example, clicking the back button would load content from Redux before requesting the new stuff.

You can choose to ignore it, BUT if you take the time to care for it, you can do some really nifty things. Like optimistic updates. Anything under pages[somekey].data belongs to you. So make edits to it! Watch your React page respond to it. I think making edits to JSON is better than making edits to raw HTML.

Forms

Dave: 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?

Forms is the next thing I’m working on. It was a thoughtbot Ralphapalooza project and I’ve just released and early version.

FormProps is a Rails form builder that outputs HTML attributes instead of tags. Now you can enjoy the conveniences of Rails helpers in other view libraries like React, and React Native.

ERB and React

Dave: Can I render ERB code inside a React component?

You used to be able to, but it was a hack. Superglue has a deprecated React component called <RailsTag />. You could write ERB or HTML in the props file and assign the result to a JSON field. It would send the HTML string to React via JSON, and the RailsTag component could render it. It used dangerouslySetInnerHtml which isn’t too dangerous if you’re intentional about it.

But <RailsTag /> is going away because it’s not needed now that I have some new features in place. I don’t need ERB any more and theres a clean separation between content and markup.

That’s what FormProps is all about.

I’ve translated Rails’ form_with form helper to output JSON representing the form. Rather than using form helpers, you use React components that can read in the props and render the form. The designer can decide what form elements look like. This will be much easier than building a custom formbuilder yourself. Your existing knowledge of how form_with works is still relevant - Superglue’s version will work the same way.

Simple Form and Bootstrap Form hide the HTML form you by having you use a DSL. But with form_props you wouldn’t have to do that. Just send the form props and have your designer decide how it looks.

Now that I have a proper form library, I can support advanced features like server-side rendering.

I also want to make Superglue versions of button_to and link_to.

When to use Superglue

Dave: So the big question is: when would I use Superglue on a new project?

My goal is to bring a React workflow that is so close to the Rails way, so close to classic multi-page applications, so close to intuitiveness you feel when you run rails g scaffold, use form_with or link_to; that it makes React competitive to the developers considering Hotwire for their project.

Finale

In this short chat with Johny I’ve learned about the inner workings of Rails, JSON processing, and MVC architecture.

As I quizzed Johny about the minutia of Superglue it became obvious that he’s thought deeply about this framework and how it fits into the Rails and React ecosystem. I can’t wait to see what features he adds next!