redirect_to不生效:Stimulus请求后UI未自动更新问题
问题
我在Rails后端写了publish和unpublish控制器方法,执行后调用redirect_to跳转到编辑页;前端用Stimulus控制器通过fetch发送PUT请求。路由和页面调用都正常,数据库状态会更新,但点击“发布”或“取消发布”后UI不会自动刷新,必须手动刷新页面才能看到状态变化。原本以为redirect_to能实现UI自动更新,但没生效,想解决无需手动刷新同步UI状态的问题。
原代码
后端控制器
def publish post = Post.find(params[:post_id]) post.update(published: true, published_at: Time.now) redirect_to edit_post_path(post) end def unpublish post = Post.find(params[:post_id]) post.update(published: false, published_at: nil) redirect_to edit_post_path(post) end
Stimulus控制器
import { Controller } from "@hotwired/stimulus" import log from "stimulus_reflex/javascript/log"; // Connects to data-controller="publishers" export default class extends Controller { connect() { console.log("Connected") } publish(event){ event.preventDefault(); const postId = event.currentTarget.getAttribute("data-post-id"); const url = `/posts/${postId}/publish`; fetch(url, { method: "PUT", headers: { "Accept": "application/json", "Content-Type": "application/json", "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, }) } unpublish(event){ event.preventDefault(); const postId = event.currentTarget.getAttribute('data-post-id'); const url = `/posts/${postId}/unpublish`; fetch(url, { method: "PUT", headers: { "Accept": "application/json", "Content-Type": "application/json", "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, }) } }
路由配置(route.rb)
scope module: "authors" do resources :posts do put "publish", to: "posts#publish" put "unpublish", to: "posts#unpublish" resources :elements do end end end
页面模板(edit.html.erb)
<div class="card-footer"> <% if @post.published? %> <a href="#" class="btn btn-danger d-block my-4" data-action="click->publishers#unpublish" data-post-id="<%= @post.id %>" >Unpublish</a> <% else %> <a href="#" class="btn btn-success d-block my-4" data-action="click->publishers#publish" data-post-id="<%= @post.id %>" >Publish</a> <% end %> </div>
问题原因
用fetch发送AJAX请求时,后端的redirect_to只会返回302重定向响应,但AJAX请求不会自动触发页面跳转——浏览器只有在处理普通HTTP请求时才会执行重定向。所以前端收到响应后不会刷新页面,UI自然不会同步更新。
解决方案
方案一:后端返回JSON,前端手动更新DOM
这种方式不需要引入额外依赖,直接修改现有代码即可。
- 修改后端控制器:
把redirect_to改成返回JSON数据,告诉前端操作结果和当前发布状态:
def publish post = Post.find(params[:post_id]) post.update(published: true, published_at: Time.now) render json: { success: true, published: post.published? } end def unpublish post = Post.find(params[:post_id]) post.update(published: false, published_at: nil) render json: { success: true, published: post.published? } end
- 修改Stimulus控制器:
在fetch请求完成后,根据后端返回的状态替换按钮元素:
import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { console.log("Connected") } publish(event){ event.preventDefault(); const postId = event.currentTarget.getAttribute("data-post-id"); const url = `/posts/${postId}/publish`; fetch(url, { method: "PUT", headers: { "Accept": "application/json", "Content-Type": "application/json", "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, }) .then(response => response.json()) .then(data => { if (data.success) { // 替换成取消发布按钮 const cardFooter = event.currentTarget.closest('.card-footer'); cardFooter.innerHTML = ` <a href="#" class="btn btn-danger d-block my-4" data-action="click->publishers#unpublish" data-post-id="${postId}" >Unpublish</a> `; } }) .catch(error => console.error('Error:', error)); } unpublish(event){ event.preventDefault(); const postId = event.currentTarget.getAttribute('data-post-id'); const url = `/posts/${postId}/unpublish`; fetch(url, { method: "PUT", headers: { "Accept": "application/json", "Content-Type": "application/json", "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, }) .then(response => response.json()) .then(data => { if (data.success) { // 替换成发布按钮 const cardFooter = event.currentTarget.closest('.card-footer'); cardFooter.innerHTML = ` <a href="#" class="btn btn-success d-block my-4" data-action="click->publishers#publish" data-post-id="${postId}" >Publish</a> `; } }) .catch(error => console.error('Error:', error)); } }
方案二:用Turbo Stream实现自动DOM更新(推荐,符合Rails Hotwire生态)
如果项目已经引入Turbo,这种方式更简洁,不需要手动拼接HTML。
- 修改后端控制器:
返回Turbo Stream指令,告诉前端替换指定DOM元素:
def publish post = Post.find(params[:post_id]) post.update(published: true, published_at: Time.now) respond_to do |format| format.turbo_stream do render turbo_stream: turbo_stream.replace( "publish-button-container", partial: "authors/posts/publish_button", locals: { post: post } ) end end end def unpublish post = Post.find(params[:post_id]) post.update(published: false, published_at: nil) respond_to do |format| format.turbo_stream do render turbo_stream: turbo_stream.replace( "publish-button-container", partial: "authors/posts/publish_button", locals: { post: post } ) end end end
- 创建按钮部分模板:
新建app/views/authors/posts/_publish_button.html.erb,把按钮逻辑抽出来:
<a href="#" class="btn <%= post.published? ? 'btn-danger' : 'btn-success' %> d-block my-4" data-action="click->publishers#<%= post.published? ? 'unpublish' : 'publish' %>" data-post-id="<%= post.id %>" > <%= post.published? ? 'Unpublish' : 'Publish' %> </a>
- 修改edit.html.erb模板:
给按钮容器加上唯一ID,方便Turbo定位替换:
<div class="card-footer" id="publish-button-container"> <%= render partial: "authors/posts/publish_button", locals: { post: @post } %> </div>
- 简化Stimulus控制器:
去掉JSON相关的请求头,Turbo会自动处理Turbo Stream响应:
import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { console.log("Connected") } publish(event){ event.preventDefault(); const postId = event.currentTarget.getAttribute("data-post-id"); const url = `/posts/${postId}/publish`; fetch(url, { method: "PUT", headers: { "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, }) .catch(error => console.error('Error:', error)); } unpublish(event){ event.preventDefault(); const postId = event.currentTarget.getAttribute('data-post-id'); const url = `/posts/${postId}/unpublish`; fetch(url, { method: "PUT", headers: { "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, }) .catch(error => console.error('Error:', error)); } }
内容的提问来源于stack exchange,提问作者rubyongainz
相关产品推荐
相关产品推荐

