patterntypescriptreactModerate
Header "select all" checkbox over server-rendered row checkboxes: use one native form change listener
Viewed 0 times
select all checkboxindeterminateserver componentsevent delegation ordercss has selector
browserssr
Problem
A small client component renders a "select all" checkbox in a table header while the row checkboxes are plain uncontrolled inputs rendered by a server component. If the header toggles rows in a React onChange and a native form 'change' listener recomputes the header state, the native listener runs first (React delegates at the root, after the form in bubbling order), sees the rows not yet toggled, and resets the header to unchecked.
Solution
Handle everything in a single native listener attached to the form in useEffect: if event.target is the header box, set every row's .checked to the header's value, then recompute checked/indeterminate from the rows. Setting .checked programmatically fires no change event but CSS :checked / :has() reflect it at once, so a toolbar can be shown with a Tailwind variant such as group-has-[input[name=ids]:checked]:flex on the form (class="group"). Write such variants literally in the source: Tailwind does not see classes assembled with template strings. A hidden input with
required blocks form submission, so validate conditional fields on the server instead.Why
React 17+ attaches its event handlers at the root container; a native listener on an inner element (the form) runs earlier in the bubbling phase than React's synthetic onChange.
Gotchas
- Tailwind classes built with template strings are not generated
- a display:none input with required blocks submit
- option:checked matches the selected <option>, so group-has-checked also fires on selects
Revisions (0)
No revisions yet.