---
title: 'Superglue 2.0 Alpha: React ♥️ Rails Turbo Streams!'
teaser: Superglue 2.0 incoming! Bringing the power of Rails Turbo Streams to React.
tags: ruby,web,ruby on rails,react,redux,javascript,hotwire,stimulus,turbo
author: Johny Ho
published_on: 2025-09-17
---

![logo](https://images.thoughtbot.com/nhow0vowwj281dh662paux0wzxjp_logo.png)

Turbo Streams is pretty awesome.

It’s a great tool to make surgical updates to your pages over websocket/SSE.
You can easily build applications that require live updates with the kind of
minimal effort that would make the modern Javascript developer blush. And
even more impressive, it’s made possible by reusing your existing Rails view
partials, giving it [essentially 8 new super powers](https://turbo.hotwired.dev/reference/streams).

It’s a fine tool to have. I must have it.

Earlier this year, we [announced](https://thoughtbot.com/blog/superglue-1-0-react-rails-a-new-era-of-thoughtfulness) Superglue: The Rails Way of building React and
Rails applications. In the same spirit, today we’re announcing [Superglue 2.0
Alpha and our new tooling: Super Turbo Streams](https://thoughtbot.github.io/superglue/2.0.alpha/), a port of Turbo Streams made for
Superglue.

## Why?

Javascript fatigue. Turbo Stream's promise of writing no additional javascript
for streaming surgical updates is an amazing proposition, and if we can bring
that to React, that would be -- thoughtful. React doesn't have a strong
streaming story with Rails. There's nothing out-of-the-box like Turbo, and
rolling your own is tedious, error prone, and without well-shaped state, you're
often left figuring out how to put everything together.

## How does it work?

Like Turbo Streams, the key to Super Turbo Streams “is the ability to reuse
your existing server-side templates to perform live, partial page changes.” The
difference is the delivery mechanism, instead of HTML, its JSON, i.e., we’ll
be using `_post.json.props` instead of `_post.html.erb`. Here’s an example

Lets assume you have

```jsx
// app/views/posts/index.jsx
import React from 'react'
import {
  useContent
} from '@thoughtbot/superglue'

export default function PostIndex() {
  const {
    favPost,
    numOfPosts
  } = useContent()

  return (
    <div>
      <h1>{`There are ${numOfPosts}`}</h1>
      <Post {...favPost} />
    </div>
  )
}
```

and the equivalent `index.json.props`

```ruby
json.favPost do
  json.title @post.title
  json.body @post.body
end

json.numOfPosts Post.count
```

Here's what we do:

First, lets generate the channel props to stream from using `stream_from_props`,
the Superglue equivalent of `turbo_stream_from`.

```diff
+ json.streamFromPosts stream_from_props('my_posts')

+ json.favPost(partial: ["post", fragment: "post-#{@post.id}"]) do
- json.favPost do
-   json.title @post.title
-   json.body @post.body
end

json.numOfPosts Post.count
```

We'll also need to extract a partial and give it a fragment name, e.g. `post-1`.
A fragment is a rendered partial with a frontend identity. You can think of it
as a DOM id, but for JSON.

```ruby
# app/views/posts/_post.json.props

json.title @post.title
json.body @post.body
```

Then import `useStreamSource` and pass the channel props.

```diff
// app/views/posts/index.jsx
import React from 'react'
import {
+ useStreamSource,
  useContent
} from '@thoughtbot/superglue'

export default function PostIndex() {
  const {
+   streamFromPosts,
    favPost,
    numOfPosts
  } = useContent()
+  useStreamSource(streamFromPosts)

  return (
    <div>
      <h1>{`There are ${numOfPosts}`}</h1>
      <Post {...favPost} />
    </div>
  )
}
```

And finally update it using `broadcast_save_later_to` (the combined equivalent of
Turbo's `broadcast_replace_later_to` and `broadcast_replace_later_to`) from your controller, job, etc.

```ruby
# Target using the fragment id from earlier
@post.broadcast_save_later_to('my_posts', target: "post-#{@post.id}")
```

## Stream Responses

Streaming responses also work well. Here's an example controller action:

```ruby
  def update
    .....

    if @post.save
      respond_to do |format|
        format.html { redirect_to posts_path, notice: "Successfully updated" }
        format.json {
          flash.now[:notice] = "Successfully submitted"
          render layout: "stream" # The stream layout is generated for you
        }
      end
    else
      redirect_to posts_path, alert: "Could not update"
    end
  end
```

and the view `update.json.props`

```ruby
broadcast_save_props(model: @post, target: "post-#{@post.id}")
```

## Breaking changes

We caution that while in alpha, Superglue's API may change. In its current
state, Super Turbo Streams only supports 4 actions (prepend, append, save,
refresh), we're working to expand that number.

2.0 is also a backward breaking change from 1.0 as we're redefining the idea of
a fragment. A migration plan will be included in a major release.

## We're not done.

We're working on more ways to make React and Rails play nice without sacrificing
Rails conventions. Stay tuned!
