Jonny Burdis
Jonny Burdis

Reputation: 13

How can I add toggle a favourite button with Stimulus and Turbo Streams?

In my Rails app, I'm trying to add a favourite feature that allows users to toggle between favouriting and unfavouriting on a show page. When favouriting, I'm getting the error 'ActionView::MissingTemplate (Missing partial favourites/_toggle_button...' in my server logs but the partial is being rendered correctly on the show page without clicking the favourite button. I'm also getting an internal server error (500) in the console when inspecting the page.

When clicked the favourite button's colour should fill, which does happen when the page is reloaded but I'm trying to make it instant, and the cinema should be added to the user's favourites. The only part which isn't working is the instant colour change of the favourite icon when clicked.

app/views/favourites/_toggle_button.html.erb

<div id="toggle-favourite-<%= cinema.id %>">
  <% is_favorited = current_user.favourites.exists?(cinema: cinema) %>
  <%= button_to(
        is_favorited ? cinema_favourite_path(cinema, current_user.favourites.find_by(cinema: cinema)) : cinema_favourites_path(cinema),
        method: is_favorited ? :delete : :post,
        remote: true,
        data: { action: "favourite#toggle" },
        class: 'favourite-toggle-form'
      ) do %>
    <i data-favourite-target="star" class="fa-<%= is_favorited ? 'solid' : 'regular' %> fa-star favourite-toggle-btn <%= 'active' if is_favorited %>"></i>
  <% end %>
</div>

app/views/cinemas/show.html.erb

<div data-controller="favourite" class="cinema-show-links">

    <div class="show-add-favourite" id="toggle-favourite-<%= @cinema.id %>">

      <p class="review-new-btn">
        <%= link_to "Add review", new_cinema_review_path(@cinema), class: 'review-new-btn' %>
      </p>

      <%= render 'favourites/toggle_button', cinema: @cinema %>

    </div>

    <%# other links %>
</div>

app/controllers/favourites_controller.rb

class FavouritesController < ApplicationController
  before_action :set_cinema, only: %i[create destroy]

  def index
    @favourites = current_user.favourites
  end

  def create
    @favourite = current_user.favourites.new(cinema: @cinema)
    if @favourite.save
      Rails.logger.info "Favourite created successfully for cinema #{@cinema.id}"
      respond_to do |format|
        format.turbo_stream
        format.html { redirect_to cinema_path(@cinema) }
      end
    else
      Rails.logger.error "Error creating favourite: #{@favourite.errors.full_messages.join(", ")}"
      render json: { error: 'Could not add to favourites.' }, status: :unprocessable_entity
    end
  end

  def destroy
    @favourite = current_user.favourites.find_by(cinema: @cinema)
    if @favourite&.destroy
      Rails.logger.info "Favourite removed successfully for cinema #{@cinema.id}"
      respond_to do |format|
        format.turbo_stream
        format.html { redirect_to cinema_path(@cinema) }
      end
    else
      Rails.logger.error "Error removing favourite: #{@favourite&.errors&.full_messages&.join(", ") || 'Not found'}"
      render json: { error: 'Could not remove from favourites.' }, status: :unprocessable_entity
    end
  end

  private

  def set_cinema
    @cinema = Cinema.find(params[:cinema_id])
  end
end

app/views/favourites/create.turbo_stream.erb

<turbo-stream action="replace" target="toggle-favourite-<%= @cinema.id %>">
  <template>
    <%= render 'favourites/toggle_button', cinema: @cinema %>
  </template>
</turbo-stream>

app/views/favourites/destroy.turbo_stream.erb

<turbo-stream action="replace" target="toggle-favourite-<%= @cinema.id %>">
  <template>
    <%= render 'favourites/toggle_button', cinema: @cinema %>
  </template>
</turbo-stream>

app/javascript/controllers/favourite_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["star"]

  connect() {
    // this.element.textContent = "Hello World!"
  }

  toggle(event) {
    event.preventDefault()
    const form = event.currentTarget.closest("form")

    fetch(form.action, {
      method: form.method,
      headers: { "X-Requested-With": "XMLHttpRequest" },
      body: new FormData(form)
    })
    .catch(error => console.error("Failed to toggle favorite:", error))
  }
}

I've tried tweaking the path of the partial to but I still get the same error that it can't be found.

Causes:

ActionView::MissingTemplate (Missing partial favourites/_toggle_button with {:locale=>[:en], :formats=>[:turbo_stream], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :jbuilder]}.

Upvotes: 0

Views: 80

Answers (0)

Related Questions