"use client"; import { useEffect, useLayoutEffect, useRef, type InputHTMLAttributes, type RefObject, type SelectHTMLAttributes, } from "react"; // A CONTROLLED CHECKBOX, RADIO OR SELECT INSIDE AN ACTION FORM (release 16, // slice FK). // // React resets a `
` when its action's transition commits (see // lib/formState.ts). A controlled TEXT input survives that: React keeps its // `defaultValue` in step with `value`, so `form.reset()` puts the same text // back. It does not do the same for the other two kinds: // - a checkbox or radio's `defaultChecked` is set only when it MOUNTS // (react-dom `initInput`; `updateInput` sets `checked` alone); // - a controlled select's options never get `defaultSelected` // (`updateOptions` is called with `setDefaultSelected` false at mount and // on every update), so the reset selects the option the SERVER rendered // `selected`, or the first option when the select mounted on the client. // So after ANY submit — failure or success — the reset returns a checkbox or // radio to what it mounted with, and a select to that option, while React's // state still says otherwise, until the next re-render that reaches it // re-asserts the prop (the editor's auto-refresh, 5 s by default; never, with // it off). A NAMED one submitted in that window posts the stale choice. // // These put the controlled value back right after the form resets: a `reset` // listener on the element's form, deferred a microtask so it runs after the // reset (React calls `form.reset()` inside its commit). They touch the DOM // ONLY then — never at mount, so a choice made before hydration (which React // replays as a change event) is not overwritten. Same props, same markup as // the bare element. function useValueAfterReset( ref: RefObject, value: V, put: (el: E, value: V) => void, ) { // The value of the LATEST commit, read when the microtask runs: a value that // changes in the very commit that resets (a form re-reading what was stored // after a save) is the one put back. const latest = useRef(value); useLayoutEffect(() => { latest.current = value; }); useEffect(() => { const el = ref.current; const form = el?.form; if (!el || !form) return; const onReset = () => queueMicrotask(() => put(el, latest.current)); form.addEventListener("reset", onReset); return () => form.removeEventListener("reset", onReset); }, [ref, put]); } const putChecked = (el: HTMLInputElement, checked: boolean) => { el.checked = checked; }; const putValue = (el: HTMLSelectElement, value: string) => { el.value = value; }; export function ControlledCheck( props: InputHTMLAttributes & { type: "checkbox" | "radio"; checked: boolean; }, ) { const ref = useRef(null); useValueAfterReset(ref, props.checked, putChecked); return ; } export function ControlledSelect( props: SelectHTMLAttributes & { value: string }, ) { const ref = useRef(null); useValueAfterReset(ref, props.value, putValue); return