DEV Community

Peter Phillips
Peter Phillips

Posted on

Toggling page content with Turbo Frames and Kredis

Wanting to show/hide content on a page is an incredibly common task, and we're probably all familiar with the traditional ways of doing this using JavaScript. While there's nothing wrong with this, it may not be the best approach in all cases. For example, one major drawback is that if you reload the page, it will be back in its default state. Something you've hidden will be shown again, or vice versa.

So what if we want something we hide to stay hidden the next time we come back to a page? 🤔

I'm glad you asked! That's exactly what I'm going to show you how to do in Rails, using Turbo Frames and the newly released gem Kredis. We'll be toggling content in a way that persists across page refreshes/navigation, is super quick to implement, and best of all, requires 0 lines of JavaScript 🙌.

Prerequesites: You'll need a Rails application, version 6 or higher, with Hotwire/Turbo installed and a Redis connection. It should also have authentication in place (Devise, etc.) and a User model.

The main elements of this solution are as follows:

  1. Use Kredis to add a Redis-backed attribute on our User model to store the state
  2. Wrap the content in a turbo_frame_tag, including a link that will function as a hide/show button
  3. Update the Kredis attribute in our controller and re-render the turbo frame

That's it!
So for our example, let's say we have a chart on our Dashboard. Some users may want to see it every time they log in, others might prefer to have it hidden.

Step 1: add a Kredis attribute to our User model

Once you've gone through the simple steps to install the Kredis gem, we're ready to add the Kredis attribute to our User model.

class User < ApplicationRecord
  ...
  kredis_flag :hide_dashboard_chart
  ...
end
Enter fullscreen mode Exit fullscreen mode

Note: As of the writing of this post, Kredis does not provide a kredis_boolean attribute, which would make more sense here. There's a PR up for this now, but using a flag in the meantime works fine.

I've opted for hide here since we want the default behavior to be showing the chart, and we'll let users opt-in to hiding it.

Step 2: Wrap the content we want to show/hide in a turbo_frame_tag

# app/views/pages/dashboard.html.erb

<%= turbo_frame_tag "dashboard_chart" do %>
  <div class="flex justify-between">
    <h3 class="text-lg font-bold">
      Q1 Growth
    </h3>
    <%= link_to current_user.hide_dashboard_chart.marked? ? "Show" : "Hide", toggle_dashboard_chart_path %>
  </div>
  <% if !current_user.hide_dashboard_chart.marked? %>
    <div class="flex justify-center">
      <img src="https://quickchart.io/chart?width=400&height=240&chart={type:'bar',data:{labels:['January','February', 'March','April', 'May'], datasets:[{label:'Dogs',data:[50,60,70,180,190]},{label:'Cats',data:[100,200,300,400,500]}]}}" />
    </div>
  <% end %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

So what's going on here?
We have a turbo_frame_tag which we've given a name of "dashboard_chart". Inside this frame, there is a top portion with a header and a link. The link shows the words "Show" or "Hide" depending on the value of hide_dashboard_chart on the current_user. This link points to our toggle_dashboard_chart_path, which we'll take a closer look at in a moment.

Below that is the chart itself, for which I'm using a very simple link-based quickchart. If our hide_dashboard_chart kredis attribute is marked?, we don't show the chart at all.

Reminder: the marked? syntax is just a result of having to use a kredis_flag for the time being.

We're also using Tailwindcss for some very light styling. It'll look something like this:
screenshot of the page with a chart

Step 3: Update the Kredis attribute in our controller and re-render the turbo frame

We'll need to add a route for the link in our turbo frame

# config/routes.rb

get "toggle_dashboard_chart", to: "pages#toggle_dashboard_chart"

Enter fullscreen mode Exit fullscreen mode

Also, let's go ahead and put the view content from Step 2 in a partial.

# app/views/pages/dashboard.html.erb

<%= render partial: "dashboard_chart" %>

Enter fullscreen mode Exit fullscreen mode

Let's write a helper method on the User model

class User < ApplicationRecord
  ...
  def toggle_hide_dashboard_chart!
    hide_dashboard_chart.marked? ? hide_dashboard_chart.remove : hide_dashboard_chart.mark
  end
end
Enter fullscreen mode Exit fullscreen mode

Then put it all together the controller

# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  ...

  def toggle_dashboard_chart
    current_user.toggle_hide_dashboard_chart!
    render partial: "dashboard_chart"
  end
end
Enter fullscreen mode Exit fullscreen mode

Believe it or not, we're done!
Now when we click the link in the Turbo frame, we'll make a round trip to the server where:

  • we toggle the hide_dashboard_chart Kredis attribute
  • we return that exact same dashboard_chart partial
  • Only the Turbo frame on our dashboard page updates, this time with the new state, showing/hiding the chart accordingly.

Let's see it in action! 🎥

Takeaways

I've really been enjoying using Hotwire/Turbo, and it presents the opportunity to rethink a lot of ways we handle some common interactions on the web. Hiding/showing some view content by making a round trip to the server might feel strange at first, but I think it actually presents some advantages, notably:

  • the code is super simple
  • we can easily store/reuse the user's preference after a page refresh

And while we could store these types of preferences in our main database, Redis feels much more appropriate to me. If we somehow lose it, no big deal! And we have total flexibility to change things around later (if our views change) without doing a database migration. Kredis giving us a nicer API to interact with Redis is just icing on the cake.

There are a couple downsides worth mentioning. While the round trip to the server will be imperceptible to most users, those with very slow connections might notice a delay. We also aren't doing any sort of animations to smooth things out here. The chart pops in and out, instantly moving the rest of the page up or down, and some might find that a bit jarring. But overall, I think the pros easily outweigh the cons.

Taking things further

This is fine as a simple proof of concept, but I think for use in production, it could be easily improved in a few obvious ways:

  • Replace the "Hide"/"Show" link text with some appropriate icons
  • We could certainly be a lot more RESTful, perhaps by making a POST request to something like users#update (since after all we are updating a value on the User model)

Thanks so much for reading!

Discussion (0)