Stimulus Discourse

Like button for posts that doesn't require users


#1

How does one make a like/unlike function with stimulus? I read the old school way of doing this is to create a create.js.erb file that renders a view page. I’m fairly new to Javascript so explain it like how you’d explain it to a 10 yr old. thanks :smiley:


#2

If you need the server to validate that the like/unlike happened, it’s still perfectly fine to use a *.js.erb response to insert the updated button.

You’re not using a MVC framework like Vue or React, so you’ll end up handling some server-generated content yourself either way :slight_smile:

One way of handling it with Stimulus is to catch the server-rendered content in an action responsible for updating the button.

Imagine the following button somewhere on your page:

# app/views/likes/_like.html.erb
<%= button_to 'Like', post_likes_path(@post), method: :post, remote: true, data: { action: 'ajax:success->like#updateButton' } %>

When the button is pressed, the request is handled by the LikesController:

# app/controllers/likes_controller.rb
class LikesController < ApplicationController
  def create
    @like = @post.likes.create

    render partial: 'unlike'
  end

  def destroy
    @like.destroy

    render partial: 'like'
  end
end

It creates the like and renders a partial containing the toggled button

# app/views/likes/_unlike.html.erb
<%= button_to 'Unlike', post_like_path(@post, @like), method: :delete, data: { action: 'ajax:success->like#updateButton' } %>

The browser itself doesn’t know what to do with the piece of server-generated HTML, but our Stimulus controller handles the event due to the ajax:success event listener attached to the button.

It replaces the button inside the controller action like this:

// app/javascript/packs/controllers/like_controller.js
export default class extends Controller {
  updateButton(event) {
    let [data, status, xhr] = event.detail;
    this.currentTarget.replaceWith(xhr.response);
  }
}

Note that the function ChildNode.replaceWith() is experimental and might not work in all browsers: https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith

While it’s absolutely possible to handle a case like this with Stimulus, I’m not sure I’d call this a win, compared to just replacing the button directly with a JavaScript response:

# app/views/likes/create.js.erb
document.getElementById("<%= dom_id(@like) %>").replaceWith("<%= j render partial: 'unlike' %>")

I hope this helped a bit :slight_smile:


#3

This reply is a win! Thank you, you kind soul. I was looking at how instagram built heart like button in a stimulus way. I’ve heard much praise from this framework so i decided to try, before this i was on that vanilla/coffee/jquery thing and that was confusing :sweat_smile:


#4

You’re welcome! Stimulus is really well suited for progressively enhancing you app with JavaScript in the places where you need it. It doesn’t take over your entire front-end, which is also why you have to handle things such as requesting/receiving data from a server yourself.

Personally I think it’s great. It’s one of the least invasive JavaScript frameworks out there :slight_smile: It fits well into existing application and it’s easy to get started with, because there are relatively few concepts to grasp.