Implement dynamic form updates using Turbo Streams and Stimulus. Use when forms need to update fields based on user selections without full page reloads, such as cascading dropdowns, conditional fields, or dynamic option lists.
86
82%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
This skill documents the turbo fetch pattern for dynamically updating form fields based on user input using Turbo Streams and Stimulus.
Use turbo fetch when you need to:
The turbo fetch pattern consists of four components:
In config/routes.rb, ensure the turbo_fetch concern exists:
concern :turbo_fetch do
patch :turbo_fetch, on: :collection
endThen apply it to your resource:
resources :materials, concerns: %i[turbo_fetch]This creates a route: PATCH /materials/turbo_fetch
Add a turbo_fetch action to your controller:
class MaterialsController < ApplicationController
def turbo_fetch
@material = authorize Material.new(material_params)
# The view will handle the turbo stream responses
end
private
def material_params
params.require(:material).permit(:type, :substance, ...)
end
endKey points:
Create app/views/[resource]/turbo_fetch.turbo_stream.slim:
= simple_form_for @material do |f|
= turbo_stream.update 'substance-field' do
= f.input :substance, collection: @material.substances
= turbo_stream.replace 'material_details', partial: dimension_fields_partial_path, locals: { f: }Available turbo stream actions:
turbo_stream.update - Replace the content inside an elementturbo_stream.replace - Replace the entire elementturbo_stream.append - Add content at the endturbo_stream.prepend - Add content at the beginningturbo_stream.remove - Remove an elementAdd the Stimulus controller to your form:
= simple_form_for resource, data: { controller: 'turbo-fetch', turbo_fetch_url_value: turbo_fetch_materials_url } do |f|
.form-row
= f.input :type, input_html: { data: { action: "turbo-fetch#perform" } }
#substance-field.flexible
= f.input :substance, collection: f.object.substances
#material_details
= render dimension_fields_partial_path, f: fKey attributes:
data-controller="turbo-fetch" - Activates the Stimulus controllerdata-turbo-fetch-url-value - The URL to PATCH (defaults to form action + /turbo_fetch)data-action="turbo-fetch#perform" - Triggers the fetch on field changeThe turbo_fetch_controller.js should exist at app/javascript/controllers/turbo_fetch_controller.js:
import { Controller } from '@hotwired/stimulus'
import { patch } from '@rails/request.js'
export default class extends Controller {
static values = { url: String, count: Number }
async perform({ params: { url: urlParam, query: queryParams } }) {
const body = new FormData(this.element)
if (queryParams) Object.keys(queryParams).forEach(key => body.append(key, queryParams[key]))
const response = await patch(urlParam || this.urlValue, { body, responseKind: 'turbo-stream' })
if (response.ok) this.countValue += 1
}
}Route:
resources :materials, concerns: %i[duplication turbo_fetch]Controller:
def turbo_fetch
@material = authorize Material.new(material_params)
endView (turbo_fetch.turbo_stream.slim):
= simple_form_for @material do |f|
= turbo_stream.update 'substance-field' do
= f.input :substance, collection: @material.substances, as: :tom_select, allow_create: true
= turbo_stream.replace 'material_details', partial: dimension_fields_partial_path, locals: { f: }Form:
= simple_form_for resource, data: { controller: 'turbo-fetch', turbo_fetch_url_value: turbo_fetch_materials_url } do |f|
= f.input :type, input_html: { data: { action: "turbo-fetch#perform" } }
#substance-field.flexible
= f.input :substance, collection: f.object.substances
#material_details
= render dimension_fields_partial_path, f: fWhen selecting a type, update available options in another field:
data-action="turbo-fetch#perform"#substance-field)Show/hide entire form sections based on selection:
turbo_stream.replace to swap out entire sectionsMost turbo_fetch routes are on :collection, but for nested resources with IDs:
resources :custom_parts, concerns: %i[turbo_fetch] do
patch :turbo_fetch, on: :member # For child items with IDs
endsimple_form_for to maintain form contextUpdates not appearing:
rails routes | grep turbo_fetch)Wrong data in fields:
material_params (or equivalent)Authorization errors:
turbo_fetch action runs same authorization as new/create097ad6b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.