Empty state with Turbo Stream (in Rails)

I have a question about using Turbo Steam. I have an application where a user sees vehicles. When you first open the app you’ll see a big empty state with “Wait while we add a vehicle”. Adding a vehicle is done in the background by another process.

That page has a div with an id #vehicles, so whenever there is a new record the correct partial gets added to that div by using the broadcasts_to method.

My big question is, how do I show and hide this empty state depending on the existence of a rendered vehicle.

This is the simplified version of my code:

The model:

class Vehicle < ApplicationRecord
  broadcasts_to ->(vehicle) { [vehicle.account, :vehicles] }
end

The index.html.erb

<%= turbo_stream_from current_account, :vehicles %>

<div id="vehicles">
  <%= render @vehicles %>
</div>

One possible solution is to do this in JavaScript, so if Stimulus detects that the #vehicles div is empty it shows a by default hidden <div>.

I hope you have some suggestions.

One thing you could do that is really old-school is use CSS for this. Set a background image or background SVG on the #vehicles div, targeted to the CSS selector #vehicles:empty. Remember in your HTML to make sure there isn’t even one bit of whitespace in that div in the source code, which should not be an issue with your lazy-loading pattern. Just put the close tag right after the open tag. Now, if the div has no child nodes (not text or element) it will show the background with the explanation, and the moment it has any content at all, the selector will no longer match and the image will disappear.

Walter

@walterdavis that’s a great suggestion. I was also thinking about broadcasting the empty state from a before hook in the model.

That’s another great way to go. The only difference is that if you do the CSS way, the visual representation of the state is baked into the HTML/CSS, so it’s there from the first load, and doesn’t wait on an async update.

Walter

I gave this question some thought and experimented a bunch.

Then I also looked at the source maps of Hey.com. It seems that they are using Stimulus to show/and the empty state of the inbox. So it’s always there, and shows when you have no new messages.

After some experimentation, I created a Stimulus controller that uses a JavaScript MutationObserver to watch changes in the tree. If there are changes I count the targets. If there are no targets I show the empty state.

index.html.erb:

<div id="vehicles" data-controller="empty-state">
  <div data-empty-state-target="emptyState">
    <%= render "empty_state" %>
  </div>
  <%= render @vehicles %>
</div>

_vehicle.html.erb: (simplified)

<div class="p-4" data-empty-state-target="item" id="<%= dom_id(vehicle) %>">
  ...
</div>

empty_state_controller.js:

import { Controller } from "stimulus";

export default class extends Controller {
  static targets = ["emptyState", "item"];

  connect() {
    this.observer = new MutationObserver(this.update.bind(this));
    this.observer.observe(this.element, {
      childList: true,
      attributes: false,
      subtree: true
    });
    this.update();
  }

  disconnect() {
    this.observer.disconnect();
  }

  update() {
    this.emptyStateTarget.classList.toggle("hidden", this.itemTargets.length !== 0);
  }
}

I hope this helps someone with this question.

2 Likes