Skip to content

Conversation

@bknoles
Copy link
Collaborator

@bknoles bknoles commented Nov 19, 2025

Steps

  • If you haven't already, create a user by going through the registration process
  • Click the tab on the right side of the screen to open the chat drawer
  • Scroll upwards at a moderate pace and notice the scroll bar jump as previous chat messages are added to the drawer via infinite scroll
  • Type a message and hit "Enter". Notice how the messages scroll to the bottom (if you are already scrolled near the bottom)
  • Create a second account in a private window and chat back and forth!

What is happening?

The realtime behavior is powered by ActionCable. Most of this setup is not related to Inertia at all. There is a useCable hook which would work in any React application. There are two places where Inertia is used:

Infinite Scroll

The messages in the chat drawer are rendered inside the Inertia.js <InfiniteScroll /> component. It has a few attributes set:

  • reverse tells the component to fetch the next page when you hit the top of a container (instead of fetching the previous page at the top of the container). Visually, the messages are reversed with the flex-col-reverse CSS property.
  • onlyNext tells the component not to fetch previous pages. In our case that means "don't fetch pages when we scroll all the way to the bottom of the list".
  • preserveUrl prevents the component from adding the page parameter to the URL. We don't need to be able to share links with the scroll position, so we don't need to track pagination in the URL.
  • buffer fetches the next page a little bit before we hit the top of the container. We do this to avoid hitting the top of the container and triggering a lot of "next page" calls in quick succession.

To fetch records backwards, the server just needs to sort in descending order. In our example, we're using the Pagy gem, but InertiaRails also supports Kaminari or even a custom pagination setup.

Receiving messages

Sending messages is done completely via React and ActionCable. Inertia isn't even involved. When the app receives a broadcast from ActionCable, the one Inertia line is in <Chat />:

router.prependToProp('chat_messages', chatMessage)

This is a client side visit. Unlike in #14 , here we do not want to update the page history, so we are using router.replace instead of router.push (.prependToProp is a shortcut for .replace).

Inertia is a protocol, not a framework. Most of the time we want to stay within the boundaries of Inertia request/response cycle. But not always! Websockets is a different protocol, and it is more appropriate for things like realtime chat. All we need to integrate back into Inertia is to merge the data back into Inertia's Page props. Client side visits are perfect for this! The same technique would work if we made a request to a pure JSON API outside of Inertia.

Auto scroll

In <ChatDrawer />, we have a useEffect that automatically scrolls our chat message container to the bottom when a new message comes in. This is purely React code; it has nothing to do with Inertia. But it's worth looking at. An Inertia application should have fewer useEffect calls in it. So you might wonder if you should should never have a useEffect. This is a good use case for one: we want to automatically scroll as a side effect of the chat message list growing larger. Because this update comes from outside Inertia, the code is more cohesive watching the chat messages array than trying to hook into ActionCable's receive method. It's a good rule of thumb to try to use Inertia as much as possible. But Inertia still leaves you will the full power of React when you need it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants