Search code examples
ruby-on-railshotwire-railsturbo

rails: how to refresh a list with turbo after form validation


I'm kinda following the rails tutorial to learn it and I wanted to learn with turbo.

This is my index.html.erb view:

<%= turbo_include_tags %>

<h1>Notes</h1>
<turbo-frame id="new_note">
<%= form_with(model: Note.new, url: notes_path) do |form| %>
    <div>
        <%= form.label :content %>
        <%= form.text_area :content %>
    </div>
    <%= form.submit "Create Note" %>
<% end %>
</turbo-frame>

<turbo-frame id="notes">
<div>
    <% @notes.each do |note| %>

        <div>
            <p><span><%= note.id %></span><%= note.content %></p>
            <small> <%= note.created_at %> </small>
            <%= link_to "Destroy", note_path(note), data: { turbo_method: :delete } %>          
        </div>
    <% end %>

    <%= link_to "New note", new_note_path %>
</div>
</turbo-frame>

My _list.html.erb template:

<% notes.each do |note| %>
    <div>
        <p><span><%= note.id %></span><%= note.content %></p>
        <small> <%= note.created_at %> </small>
        <%= link_to "Destroy", note_path(note), data: { turbo_method: :delete } %>          
    </div>
<% end %>

And this is my controller:

  def index
    @notes = Note.all.order(created_at: :desc)
  end
  ...
  def new
    @note = Note.new
  end

  def create
    @note = Note.new(note_params)

    if @note.save
      @notes = Note.all  # Reload all notes or fetch them as needed
      respond_to do |format|
        format.turbo_stream do
          render turbo_stream: turbo_stream.replace("notes", partial: "notes/list", locals: { notes: @notes })
        end
        format.html { redirect_to notes_url }  # Fallback in case JavaScript is disabled
      end
    else
      render :new
    end
  end
  ...

I want when clicking on the submit button to create a new note and update the list. I have two issues in my code. The first one is after the validation the form is not clean. I don't know the best way to do it.

The second one is to refresh the list. It works only one time after the first time I click on the submit button. I cannot create a second note without refreshing the page. Another issue is that the new note is added at the end of the list instead of the beginning (because my list is sorted by created_at value).


Solution

  • You to turbo_stream.update instead of turbo_stream.replace, when you replace you're loosing <turbo-frame id="notes"> which means it doesn't work the second time:

    render turbo_stream: turbo_stream.update("notes", partial: "notes/list", locals: { notes: @notes })
    

    <%= turbo_include_tags %> is a deprecated helper and should not be used.


    You don't have to re-render the entire list again. Only add the newly created note to the list:

    <!-- app/views/notes/index.html.erb -->
    
    <div id="new_note">
      <%= render "form", note: Note.new %>
    </div>
    
    <div id="notes">
      <%= render @notes %>
    </div>
    
    <!-- app/views/notes/_note.html.erb -->
    
    <div id="<%= dom_id note %>">
      <%= note.content %>
    </div>
    
    # app/controller/notes_controller.rb
    
    def create
      @note = Note.new(note_params)
    
      respond_to do |format|
        if @note.save
          format.turbo_stream do
            render turbo_stream: [
              turbo_stream.update(:new_note, partial: "notes/form", locals: {note: Note.new}),
              turbo_stream.prepend(:notes, @note),
            ]
          end
          format.html { redirect_to notes_url(@note), notice: "Note was successfully created." }
        else
          format.turbo_stream do
            render turbo_stream: [
              turbo_stream.update(:new_note, partial: "notes/form", locals: {note: @note}),
            ]
          end
          format.html { render :new, status: :unprocessable_entity }
        end
      end
    end