Как разобраться с кейсом: rEACT-HOOK-FORM. Как синхронизировать defaultValues с данными, получаемыми из react-query запроса?
По вводным: использую react-hook-form вместе с react-query. В родителе вызываю query, из полученных данных формирую defaultValues и прокидываю в компонент формы.
Agreement.tsx
export const Agreement = ({ clientId }: AgreementProps) => { const { data, isLoading, isFetching, error } = useGetServiceAgreement( { clientId, }, { meta: { isolatedErrorHandling: true, }, }, ); if (isLoading) { return ( <div className="space-y-6"> <Skeleton className="h-9 w-full" /> <Skeleton className="h-9 w-full" /> </div> ); } if (error) { return <ErrorAlert error={error} errorMapper={ErrorMapper} />; } if (!data) return null; const { description = '', clientCommissionPercent } = data; const defaultValues = { description, clientCommissionPercent, }; return ( <div className="relative p-4"> {isFetching && <LoadingOverlay />} <AgreementForm defaultValues={defaultValues} clientId={clientId} /> </div> ); };
AgreementForm.tsx
export const AgreementForm = ({ defaultValues, clientId, }: AgreementFormProps) => { const { mutateAsync: updateAgreement } = useUpdateClientServiceAgreement(); const methods = useForm<ClientAgreementFormData>({ resolver: yupResolver(ClientAgreementSchema), defaultValues: defaultValues ?? ClientAgreementSchema.getDefault(), }); const { formState, reset, handleSubmit } = methods; const { isSubmitting, isSubmitSuccessful } = formState; useEffect(() => { if (!defaultValues) return; reset(defaultValues, FORM_RESET_CONFIG); }, [reset, defaultValues, isSubmitSuccessful]); const onSubmit = async (data: ClientAgreementFormData) => { await updateAgreement({ clientId, updateClientServiceAgreementRequest: data, }); toast.success('Agreement updated successfully.'); }; return ( <Form {...methods}> <form className="space-y-6"> <AgreementFields /> <div> <Button onClick={handleSubmit(onSubmit)} isLoading={isSubmitting}> Save </Button> </div> </form> </Form> ); };
Сейчас ситуация такая: не понимаю как синхронизировать значения формы с дефолтными значениями если они обновились.
Сейчас ситуация такая: 1. react-query перезапрашивает данные на window focus (refetchOnWindowFocus: true). В этом случае кажется логичным обновить новыми значениями поля, которые не были тронуты пользователем. Для этого делаю:
useEffect(() => { if (!defaultValues) return; reset(defaultValues, FORM_RESET_CONFIG); }, [reset, defaultValues, isSubmitSuccessful]);
Где
export const FORM_RESET_CONFIG = { keepDirtyValues: true, keepErrors: true, keepTouched: true, keepIsSubmitted: true, keepSubmitCount: true, keepIsValid: true, };
По вводным: 2. Когда onSubmit успешно завершился нужно сбросить ВСЕ поля формы. В этом случае нужно сделать reset(valuesFromPostResponse) без FORM_RESET_CONFIG.
Вопросы:
Нужно понять: 1. Нужно ли действительно синхронизировать значения формы с дефолтными значениями если дефолтные значения изменились?
Нужно понять: 2. Как реализовать для одной формы одновременно 2 описанных выше кейса?
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос
В react-hook-form defaultValues читаются один раз при инициализации формы. Если данные из react-query пришли позже или поменялись после refetch, форма сама не переинициализируется. Это нормальное поведение. Для синхронизации нужно явно вызвать reset, когда пришли новые данные, либо использовать values в новых версиях react-hook-form, если вам подходит полностью управляемая синхронизация.
Самый распространенный вариант — внутри компонента формы вызвать useForm с начальными defaultValues, а потом сделать reset при изменении props. Важно не делать reset на каждый render, иначе пользователь будет терять введенные изменения.
type FormValues = { description: string; clientCommissionPercent?: number; }; export function AgreementForm({ defaultValues }: { defaultValues: FormValues }) { const form = useForm<FormValues>({ defaultValues }); const { reset } = form; useEffect(() => { reset(defaultValues); }, [defaultValues, reset]); return ( <form> {/* поля формы */} </form> ); }type FormValues = { description: string; clientCommissionPercent?: number; }; export function AgreementForm({ defaultValues }: { defaultValues: FormValues }) { const form = useForm<FormValues>({ defaultValues }); const { reset } = form; useEffect(() => { reset(defaultValues); }, [defaultValues, reset]); return ( <form> {/* поля формы */} </form> ); }
Но у этого примера есть нюанс: объект defaultValues в родителе создается заново при каждом render. Лучше мемоизировать его, чтобы reset не срабатывал лишний раз:
const defaultValues = useMemo(() => ({ description: data?.description ?? '', clientCommissionPercent: data?.clientCommissionPercent }), [data?.description, data?.clientCommissionPercent]);const defaultValues = useMemo(() => ({ description: data?.description ?? '', clientCommissionPercent: data?.clientCommissionPercent }), [data?.description, data?.clientCommissionPercent]);
Если нужно не затирать ручные изменения пользователя при фоновом refetch, используйте keepDirtyValues:
useEffect(() => { reset(defaultValues, { keepDirtyValues: true }); }, [defaultValues, reset]);useEffect(() => { reset(defaultValues, { keepDirtyValues: true }); }, [defaultValues, reset]);
Логика такая: первый fetch заполняет форму, пользователь редактирует, а фоновое обновление react-query не должно внезапно откатить поля. Если же после успешного сохранения нужно подтянуть свежие данные с сервера и сбросить dirty-state, тогда reset без keepDirtyValues уместен.
Итого: defaultValues не синхронизируются автоматически. Держите fetch в родителе, форму в отдельном компоненте, мемоизируйте значения и вызывайте reset в useEffect. Для фонового isFetching показывайте overlay, но не пересоздавайте форму через key без необходимости, иначе можно потерять локальное состояние и фокус.