2.6 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Use the values Prop to Keep a Form in Sync with Server Data | HIGH | replaces a useEffect+reset that overwrites edits whenever the query refetches | formcfg, values, resetOptions, react-query, server-state |
Use the values Prop to Keep a Form in Sync with Server Data
When the initial data arrives from a query, the reflex is useEffect(() => reset(data), [data]). That works until the query refetches — on window focus, on interval, after an unrelated mutation — and the effect fires again mid-edit, wiping whatever the user had typed.
useForm({ values }) is the built-in answer. RHF re-syncs the form when the values reference changes, and resetOptions: { keepDirtyValues: true } tells it to leave fields the user has touched alone while updating the ones they haven't. defaultValues still supplies the shape before the first response lands.
Incorrect (effect-driven reset — a background refetch discards in-progress edits):
function ProfileForm({ userId }: { userId: string }) {
const { data: profile } = useQuery({ queryKey: ['profile', userId], queryFn: fetchProfile })
const { register, reset, handleSubmit } = useForm<ProfileFormValues>({
defaultValues: { displayName: '', bio: '' },
})
useEffect(() => {
if (profile) reset(profile) // Fires again on every refetch, mid-edit
}, [profile, reset])
return (
<form onSubmit={handleSubmit(saveProfile)}>
<input {...register('displayName')} />
</form>
)
}
Correct (declarative sync that preserves what the user has touched):
function ProfileForm({ userId }: { userId: string }) {
const { data: profile } = useQuery({ queryKey: ['profile', userId], queryFn: fetchProfile })
const { register, handleSubmit } = useForm<ProfileFormValues>({
defaultValues: { displayName: '', bio: '' },
values: profile,
resetOptions: { keepDirtyValues: true },
})
return (
<form onSubmit={handleSubmit(saveProfile)}>
<input {...register('displayName')} />
</form>
)
}
Which initialiser to reach for:
defaultValues— the shape and the baseline; required regardless (seeformcfg-default-values)values— the record is fetched and may change while the form is open- async
defaultValues— the record is fetched once and will not change under the form (seeformcfg-async-default-values)
Drop keepDirtyValues only when a server change should win over the user's edit — a record another person may be editing concurrently, for instance.
Reference: useForm - values