Rails 6.1 - Hotwire with Flash Messages

A simple Rails App that works off one page using flash messages

Overview

Now that you have the basics of using Hotwire in Rails Using Hotwire in Rails - its interesting to try using it in other contexts, inparticular modals are very useful for inputs in Single Page Apps. So in this Blog we will make the new input form a modal and leave the edit as an in-place form.

Basic Setup

Start with the code at the end of: Using Hotwire in Rails

Flash Messages in Partial

Remember, turbo_streams requires a dom_id and a partial in order to know where to send / update the HTML it generates – so let’s prepare application.html.erb so that flash messages use partials.

# app/views/layouts/application.html.erb
<body>
  <%= render "shared/notice", notice: notice %>
  <%= yield %>
</body>

and of course we need a partials for notices now (we will keep it very simple):

# app/views/shared/_notice.html.erb
<p id="notice"><%= notice %></p>

now we will create a turbo template to handle the flash on create:

# app/views/tweets/create.turbo_stream.erb
<%# to send a message to the notice partial %>
<!--            action   dom_id         partial with dom_id   data to send in the notice -->
<%= turbo_stream.append "notice", partial: "shared/notice", locals: {notice: "Tweet created."} %>

In order for the controller and turbo_stream to handle this non-standard action we need to update the create method in the controller with the instructions format.turbo_stream on a successful create:

# app/controllers/tweets_controller.rb
def create
    @tweet = Tweet.new(tweet_params)

    respond_to do |format|
      if @tweet.save
        format.turbo_stream  # enables flash message on create - via the create template
        format.html { redirect_to tweets_url, notice: "Tweet was successfully created." }
        format.json { render :show, status: :created, location: @tweet }
      else
        format.turbo_stream { # route turbo validation errors
                      render turbo_stream: turbo_stream.replace(
                              @tweet, partial: "tweets/form",
                              locals: { tweet: @tweet}) }
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @tweet.errors, status: :unprocessable_entity }
      end
    end
  end

Now when we test everything works great, except our form no longer clears. We can fix that by adding a second action to the create template (we will send a Tweet.new - there are other approaches too - covered in Hotwire and StimulusJS)

# app/views/tweets/create.turbo_stream.erb
<%# clear form on create - without using JavaScript - by replacing the old Tweet info with Tweet.new %>
<%= turbo_stream.replace "tweet-form", partial: "tweets/form", locals: { tweet: Tweet.new } %>
<%# to send a message to the notice partial %>
<%= turbo_stream.append "notice", partial: "shared/notice", locals: {notice: "Tweet was successfully created."} %>

Refactor

You might have noticed, that we have moved most of our turbo_steam template to the template file, but not the replace for validation errors – since we already have a replace command in our template - we will need to leave our specific instructions in the errors as is – until we clear the form with JS.

NOTE: now that we are consolidating our template info it might be tempting to add the following:

<!-- to prepend on create - disabled to avoid double vision when broadcasting -->
<%#%   stream_action   dom_id_target, render_partial,       send_local_variables   %>
<%= turbo_stream.prepend "tweets", partial: "tweets/tweet", locals: { tweet: @tweet } %>

but don’t add the default happy path instructions to the template when a model already has a broadcast after hook - if you add this instruction the person creating a new tweet will see two!

Flash after we update

This is now very straight forward we simply add format.turbo_stream to our save and create an update.turbo_stream.erb template

# app/views/tweets/update.turbo_stream.erb
<%# to send a message to the notice partial %>
<%= turbo_stream.append "notice", partial: "shared/notice", locals: {notice: "Tweet was successfully created."} %>

And now we can tell the controller to use that:

#  app/controllers/tweets_controller.rb
  def update
    respond_to do |format|
      if @tweet.update(tweet_params)
        format.turbo_stream
        format.html { redirect_to @tweet, notice: "Tweet was successfully updated." }
        format.json { render :show, status: :ok, location: @tweet }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @tweet.errors, status: :unprocessable_entity }
      end
    end
  end

We don’t have to clear the form on update since the edit template is replaced with the show template already. So we are done.

Resources

The repo where you can find this code in the branch: https://github.com/btihen/ruby_kafi_hotwire_tweets/commits/hotwire_flash_messages

Bill Tihen
Bill Tihen
Developer, Data Enthusiast, Educator and Nature’s Friend

very curious – known to explore knownledge and nature