Jordan Lee
Jan 2026
3 tabs
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["count", "button"]
static values = {
url: String,
liked: Boolean
}
async toggle(event) {
event.preventDefault()
// Optimistic update
const previousLiked = this.likedValue
const previousCount = parseInt(this.countTarget.textContent)
this.likedValue = !previousLiked
this.countTarget.textContent = previousCount + (this.likedValue ? 1 : -1)
this.updateButtonState()
try {
const response = await fetch(this.urlValue, {
method: this.likedValue ? 'POST' : 'DELETE',
headers: {
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content,
'Accept': 'application/json'
}
})
if (!response.ok) {
throw new Error('Request failed')
}
const data = await response.json()
// Update with actual count from server
this.countTarget.textContent = data.likes_count
} catch (error) {
// Rollback on error
this.likedValue = previousLiked
this.countTarget.textContent = previousCount
this.updateButtonState()
alert('Failed to update like. Please try again.')
}
}
updateButtonState() {
if (this.likedValue) {
this.buttonTarget.classList.add('liked')
this.buttonTarget.setAttribute('aria-pressed', 'true')
} else {
this.buttonTarget.classList.remove('liked')
this.buttonTarget.setAttribute('aria-pressed', 'false')
}
}
}
<article class="post-card">
<h2><%= link_to post.title, post_path(post) %></h2>
<p><%= post.excerpt %></p>
<div class="post-actions"
data-controller="like"
data-like-url-value="<%= post_like_path(post) %>"
data-like-liked-value="<%= current_user&.liked?(post) %>">
<button type="button"
class="like-button <%= 'liked' if current_user&.liked?(post) %>"
data-like-target="button"
data-action="like#toggle"
aria-pressed="<%= current_user&.liked?(post) %>">
<i class="fas fa-heart"></i>
<span data-like-target="count"><%= post.likes_count %></span>
</button>
</div>
</article>
class LikesController < ApplicationController
before_action :authenticate_user!
def create
@post = Post.find(params[:post_id])
@like = @post.likes.create(user: current_user)
render json: { likes_count: @post.likes.count }
end
def destroy
@post = Post.find(params[:post_id])
@post.likes.where(user: current_user).destroy_all
render json: { likes_count: @post.likes.count }
end
end
3 files · javascript, erb, ruby
Explain with highlit
Waiting for server confirmation makes interfaces feel sluggish. Optimistic updates immediately show the expected result, then reconcile with the server response. When a user likes a post, I increment the count immediately via Stimulus, submit the request in the background, and handle rollback if it fails. For create operations, I append a pending item with a loading state, then replace it with the real item when the server responds. This pattern requires careful state management—I track pending operations and ensure idempotency so duplicate clicks don't create chaos. The perceived performance improvement is dramatic, but I'm conservative about which operations get optimistic treatment.
Related snips
ruby
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
rails
hotwire
turbo
by codesnips
4 tabs
ruby
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
rails
turbo
hotwire
by codesnips
4 tabs
ruby
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
rails
performance
streaming
by codesnips
3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
<select name="status" class="rounded border p-2">
<option value="">Any</option>
<option value="open">Open</option>
<option value="closed">Closed</option>
</select>
Filter UI that syncs query params via Stimulus (no front-end router)
rails
hotwire
stimulus
by Henry Kim
2 tabs
ruby
class TasksController < ApplicationController
def destroy
@task = Task.find(params[:id])
@task.destroy!
respond_to do |format|
Remove deleted items instantly with turbo_stream.remove
rails
hotwire
turbo
by Henry Kim
2 tabs
ruby
Rails.application.configure do
config.after_initialize do
Bullet.enable = true
Bullet.alert = false
Bullet.bullet_logger = true
Bullet.console = true
N+1 query detection with Bullet gem
rails
performance
activerecord
by Alex Kumar
2 tabs
Share this code
Here's the card — post it anywhere.