import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["template", "container"]
add(event) {
event.preventDefault()
const id = Date.now().toString()
const html = this.templateTarget.innerHTML.replaceAll("NEW_RECORD", id)
this.containerTarget.insertAdjacentHTML("beforeend", html)
}
remove(event) {
event.preventDefault()
const wrapper = event.target.closest("[data-nested-form-wrapper]")
if (!wrapper) return
const destroy = wrapper.querySelector("input[name*='[_destroy]']")
if (destroy) destroy.value = "1"
wrapper.hidden = true
}
}
<div data-controller="nested-form">
<template data-nested-form-target="template">
<div data-nested-form-wrapper>
<input type="text" name="snip[code_blocks_attributes][NEW_RECORD][name]" placeholder="Name" />
<input type="hidden" name="snip[code_blocks_attributes][NEW_RECORD][_destroy]" value="0" />
<a href="#" data-action="nested-form#remove">Remove</a>
</div>
</template>
<div data-nested-form-target="container"></div>
<a href="#" data-action="nested-form#add">Add code block</a>
</div>
For nested forms, Stimulus can manage the DOM while Rails handles the final params. Use a hidden template + a unique timestamp key. This keeps the server-rendered form simple and avoids JS frameworks.