Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork1.2k
Commit987bfa3
authored
Ensure form state is up to date when using uncontrolled components (#3790)
This PR fixes an issue where uncontrolled `Switch` or `Checkbox`components' form state aren't always up to date when calling the`onChange` handler.This is because the `onChange` handler is called at the same time theinternal state is updated. That means that if you submit the nearestform as part of the `onChange` handler that the form state is not up todate yet.We fix this by calling `flushSync()` before we call the `onChange`handler when dealing with an uncontrolled component.## Test planSetup a small reproduction with both a controlled and uncontrolledcheckbox<details><summary>Reproduction</summary>```tsximport { Switch } from '@headlessui/react'import { useRef, useState } from 'react'import { flushSync } from 'react-dom'export default function App() { let formRef = useRef<HTMLFormElement>(null) let [enabled, setEnabled] = useState(true) let [submisisons, setSubmissions] = useState<Array<Record<string, string>>>([]) return ( <div> <form className="p-8" ref={formRef} onSubmit={(e) => { e.preventDefault() let form = Object.fromEntries(new FormData(e.currentTarget).entries()) as Record< string, string > setSubmissions((s) => s.concat([form])) }} > <div className="flex flex-col gap-1"> <div className="flex items-center gap-2"> <Switch name="my-uncontrolled-switch" className="data-checked:bg-blue-500 aspect-square rounded border bg-white p-2" defaultChecked={true} onChange={() => { formRef.current?.requestSubmit() }} /> <div>Uncontrolled switch</div> </div> <div className="flex items-center gap-2"> <Switch name="my-controlled-switch" className="data-checked:bg-blue-500 aspect-square rounded border bg-white p-2" checked={enabled} defaultChecked={true} onChange={(v) => { // If you are controlling state yourself, then the `form` fields are // based on the incoming value, so in this case you have to call the // flushSync() yourself. flushSync(() => setEnabled(v)) formRef.current?.requestSubmit() }} /> <div>Controlled switch</div> </div> <button type="submit" className="mt-4 w-56 border"> Submit </button> </div> <hr className="my-8" /> <h3>Form submisisons:</h3> <pre>{JSON.stringify(submisisons.toReversed(), null, 2)}</pre> </form> </div> )}```</details>Before:Notice that the moment I click the `uncontrolled` checkbox, the form isnot up to date yet. Pressing submit again shows the correct value eventhough visually nothing changed anymore.https://github.com/user-attachments/assets/600eb3c2-9c56-40a7-900d-5694f391ecedAfter:Here the form state is always up to date when submitting as part of the`onChange` handler.https://github.com/user-attachments/assets/640c9ed6-69d2-4d5b-acfc-bce7d0be8828Fixes:#37601 parent030773c commit987bfa3
File tree
2 files changed
+7
-1
lines changed- packages/@headlessui-react
- src/hooks
2 files changed
+7
-1
lines changedOriginal file line number | Diff line number | Diff line change | |
---|---|---|---|
| |||
17 | 17 |
| |
18 | 18 |
| |
19 | 19 |
| |
| 20 | + | |
20 | 21 |
| |
21 | 22 |
| |
22 | 23 |
| |
|
Original file line number | Diff line number | Diff line change | |
---|---|---|---|
| |||
1 | 1 |
| |
| 2 | + | |
2 | 3 |
| |
3 | 4 |
| |
4 | 5 |
| |
| |||
33 | 34 |
| |
34 | 35 |
| |
35 | 36 |
| |
36 |
| - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
37 | 42 |
| |
38 | 43 |
| |
39 | 44 |
| |
|
0 commit comments
Comments
(0)