Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit987bfa3

Browse files
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:#3760
1 parent030773c commit987bfa3

File tree

2 files changed

+7
-1
lines changed

2 files changed

+7
-1
lines changed

‎packages/@headlessui-react/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Ensure`--button-width` and`--input-width` have the latest value ([#3786](https://github.com/tailwindlabs/headlessui/pull/3786))
1818
- Fix 'Invalid prop`data-headlessui-state` supplied to`React.Fragment`' warning ([#3788](https://github.com/tailwindlabs/headlessui/pull/3788))
1919
- Ensure`element` in`ref` callback is always connected when rendering in a`Portal` ([#3789](https://github.com/tailwindlabs/headlessui/pull/3789))
20+
- Ensure form state is up to date when using uncontrolled components ([#3790](https://github.com/tailwindlabs/headlessui/pull/3790))
2021

2122
##[2.2.7] - 2025-07-30
2223

‎packages/@headlessui-react/src/hooks/use-controllable.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import{useRef,useState}from'react'
2+
import{flushSync}from'react-dom'
23
import{useEvent}from'./use-event'
34

45
exportfunctionuseControllable<T>(
@@ -33,7 +34,11 @@ export function useControllable<T>(
3334
if(isControlled){
3435
returnonChange?.(value)
3536
}else{
36-
setInternalValue(value)
37+
// Ensure internal state is up to date with the value, before calling
38+
// onChange. This allows you to submit forms as part of the `onChange`
39+
// and gives enough time to update the form field value(s).
40+
flushSync(()=>setInternalValue(value))
41+
3742
returnonChange?.(value)
3843
}
3944
}),

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp