Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 4188248664b77aaad2d55807ee9c42d110792b84
parent f63bdce033bb271e9155d5c331b6c1b09d949175
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 02:00:21 -0400

editor: ControlledCheck/ControlledSelect put the value back after the reset only — never at mount

The first cut kept the DOM default in step in a layout effect that also wrote
the value on every commit, mount included. A choice made before hydration —
which React replays as a change event once it hydrates the element — was
overwritten by that first write, so the replayed change read the old value and
nothing changed: cadence-ui's three specs and channel-site-membership's create
case selected an option or ticked a box straight after goto and the choice was
lost.

Now a `reset` listener on the element's form puts the latest committed value
back, a microtask after the reset (React calls form.reset() inside its commit;
the layout effect only records the value). Nothing touches the DOM at mount or
on any other commit. Measured again in Chromium on react-dom 19.2.4 under
StrictMode, including a value that changes in the very commit that resets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/components/forms/Controlled.tsx | 65++++++++++++++++++++++++++++++++++++++++++-----------------------
1 file changed, 42 insertions(+), 23 deletions(-)

diff --git a/editor/app/components/forms/Controlled.tsx b/editor/app/components/forms/Controlled.tsx @@ -1,9 +1,11 @@ "use client"; import { + useEffect, useLayoutEffect, useRef, type InputHTMLAttributes, + type RefObject, type SelectHTMLAttributes, } from "react"; @@ -19,15 +21,46 @@ import { // - a select's options' `defaultSelected` likewise (`updateOptions` is // called with `setDefaultSelected` false on every update). // So after ANY submit — failure or success — the reset returns the element to -// what it mounted with while React's state still says otherwise, and nothing -// re-renders it: the box shows the old choice, and a NAMED one posts the old -// choice on the next submit. Measured in a browser against react-dom 19.2.4, -// and the same code is in Next's bundled react-dom. +// what it mounted with 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 keep the DOM's default in step with the controlled value after every -// commit, and re-assert the value itself: a layout effect runs after the -// commit's mutations, and the reset is one of them. Same props, same markup as +// 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<E extends HTMLInputElement | HTMLSelectElement, V>( + ref: RefObject<E | null>, + 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<HTMLInputElement> & { type: "checkbox" | "radio"; @@ -35,13 +68,7 @@ export function ControlledCheck( }, ) { const ref = useRef<HTMLInputElement>(null); - const { checked } = props; - useLayoutEffect(() => { - const el = ref.current; - if (!el) return; - el.defaultChecked = checked; - el.checked = checked; - }); + useValueAfterReset(ref, props.checked, putChecked); return <input ref={ref} {...props} />; } @@ -49,14 +76,6 @@ export function ControlledSelect( props: SelectHTMLAttributes<HTMLSelectElement> & { value: string }, ) { const ref = useRef<HTMLSelectElement>(null); - const { value } = props; - useLayoutEffect(() => { - const el = ref.current; - if (!el) return; - for (const option of el.options) { - option.defaultSelected = option.value === value; - } - el.value = value; - }); + useValueAfterReset(ref, props.value, putValue); return <select ref={ref} {...props} />; }