Rails, turbo frame, flash and tooltips

I am banging my head here not being able to do seemingly quite straightforward thing.
Granted, i am still new to turbo, specially when it comes to rails.

So, what i would like to achieve is:

  • person clicks on a “Like” component
  • replace this same component with new count and other logic returned by controller
  • display flash message to inform user it was successfull
  • this component has a Bootstrap Tooltip on it

So, first i though, i need turbo stream. So i created link_to, which i though will request a turbo_stream response. I was wrong. I searched and found some people do link_to :get as workaround but that doesn’t work either for me.

Then, i went and created turbo-frame and it works perfectly, except that i can do only one render, meaning i loose flash message - i can only update the Like component. I can also do event listener on frame-loaded to initialize a tooltip on newly rendered component. But i still miss flash.

Then i though OK, let’s go with stimulus controller, and send a turbo_stream request.
Fine, I can now render Flash + replace component, but now i don’t know how to initialize a Tooltip on the rendered component.

Whichever way I go, i miss something.
Would anyone be so kind and explain what is correct way, or best way?

So to repeat, the component (be it button_to, link_to, something else) needs to be updated in-place from controller, tooltip needs to work after it has been rendered, and i also need to show flash message.

Here’s what I’m thinking, real quick (I might have better ideas later):

  1. In the turbo_frame that has the like button, you’ll have a button that posts the “like” and the resulting page will have the updated “like” turbo_frame. That will update that portion of the page automatically.
  2. For the tooltip, you’ll want to wrap that component in a Stimulus controller that properly wires up the $(element).tooltip() on connect() and properly dismounts the tooltip on disconnect().
  3. To update another part of the page with a flash message, instead of returning a format.turbo_stream, you can manually return a turbo_stream.replace tag as part of your updated turbo_frame. Include the notice inside the turbo_stream.replace and have it target the #notices div elsewhere on the page.
<%= turbo_frame_tag dom_id(@like) do %>
  <!-- The Like HTML -->
  <% if notice.present? %>
    <%= turbo_stream.replace "#notices" do %>
      <!-- new notices go here -->
    <% end %>
  <% end %>
<% end %>

Hey @pascallaliberte , thanks for the reply. After a few hours of sleep and fresh mind in the morning, i was able to do exactly what you said, with one little difference. The missing part was the turbo-stream element inside the turbo-frame. Somehow, i thought i am limited to one or the other. It works as expected. I am rendering ViewComponent, so it’s even cleaner (in my mind). The difference is how i do the tooltip. Basically, i reinitialize all of them on turbo-frame-load event. And it works fine. But maybe stimulus approach would be better as i would do it on one element only and it would be cleaner.

But, if you have any other ideas, don’t hesitate. Always room to improve. :slight_smile:

@svashtar awesome.

For the tooltip, the disadvantage of reinitializing all tooltips on turbo-frame-load is that for long-running window sessions (as Turbo facilitates by just constantly replacing the body), you end up having a lot of orphaned event handlers that are never properly disposed of.

So by using Stimulus’ connect() and disconnect() lifecycle callbacks to instantiate and destroy Bootstrap tooltip instances, you prevent Javascript memory usage buildup.

I’m assuming here you’d have to use either .tooltip('disable') or .tooltip('destroy') on disconnect().

1 Like

@pascallaliberte Thanks for this, works a charm. Clean, efficient and proper solution. Like it a lot.
Somehow i am “afraid” that with large projects the number of stimulus controllers will balloon, but i guess that is not necessarily a bad thing. Time will tell.

1 Like