我试图使用React Hook- form和React Query创建一个表单,每当用户更改任何字段(取消)时,都会自动生成该表单。我正在接近,但当我变异时,它会产生一个无限的循环。以下是我所拥有的:
"@tanstack/react-query": "^4.2.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.34.2",import React from 'react'
import { useMutation, useQuery } from '@tanstack/react-query'
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as Yup from 'yup'
import debounce from 'just-debounce-it'
type NameType = {
id: number
firstName: string
lastName: string
}
const schemaValidation = Yup.object().shape({
id: Yup.number().required('Required'),
firstName: Yup.string().required('Required'),
lastName: Yup.string()
.min(2, 'Must be greater than 1 character')
.max(50, 'Must be less than 50 characters')
})
const getMockData = async () => {
const name: NameType = {
id: 1,
firstName: 'John',
lastName: 'Doe'
}
return await Promise.resolve(name)
}
const saveChangeToDatabase = async (args: NameType) => {
console.count('payload for patch:' + JSON.stringify(args))
return await Promise.resolve(args)
}
const NameForm = () => {
const queryResult = useQuery(['user'], getMockData)
const mutationResult = useMutation(saveChangeToDatabase, {
onSuccess: (nameToSave: NameType) => {
console.count('success mutating: ' + JSON.stringify(nameToSave))
}
})
const {
register,
reset,
watch,
formState: { isValid, isDirty, errors }
} = useForm<NameType>({
mode: 'all',
criteriaMode: 'all',
resolver: yupResolver(schemaValidation)
})
const fieldData = watch()
const handleDebouncedChange = debounce((data: NameType) => {
mutationResult.mutateAsync(data)
}, 500)
React.useEffect(() => {
reset(queryResult.data)
}, [queryResult.data])
React.useEffect(() => {
if (isValid && isDirty) {
handleDebouncedChange(fieldData)
}
}, [fieldData, isValid, isDirty])
if (queryResult.isLoading) {
return <h2>Loading...</h2>
}
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
margin: 'auto',
width: 300
}}>
<input {...register('firstName')} placeholder='First name' />
<div style={{ color: 'red' }}>{errors && errors?.firstName?.message}</div>
<input {...register('lastName')} placeholder='Last name' />
<div style={{ color: 'red' }}>{errors && errors?.lastName?.message}</div>
{'Field data: ' + JSON.stringify(fieldData)}
</div>
)
}
export default NameForm我还在这里复制了一个创建-反应-应用程序。您可以克隆回购,运行npm,npm,当您更改表单时会看到问题。这是您唯一需要查看的页面:
https://github.com/k-38/react-query_react-hook-form_autosave/blob/main/src/NameForm.tsx任何帮助都很感谢,谢谢。
更新:非常感谢你的回答。我接受了您的答案,并在一个自定义钩子中抽象出了许多退出/回调逻辑(尚未输入):
import React from 'react'
import debounce from 'just-debounce-it'
export function useDebouncedAutoSave({
mutationResult,
validationSchema,
getValues,
reset,
queryResult
}: {
mutationResult: any
validationSchema: any
getValues: any
reset: any
queryResult: any
}) {
React.useEffect(() => {
reset(queryResult.data)
}, [queryResult.data])
const handleDebouncedChange = React.useMemo(
() =>
debounce((data: any) => {
mutationResult.mutateAsync(data)
}, 500),
[mutationResult.mutateAsync]
)
const onChange = async () => {
try {
const values = getValues()
const validated = await validationSchema.validate(values)
if (!validated) return
handleDebouncedChange(validated)
} catch (e) {}
}
return {
onChange
}
}发布于 2022-09-05 22:44:06
因此,在带有useEffect的功能组件中,无限循环通常是由于每个“循环周期”上的依赖值发生了变化。
在文档中,我们可以读到:
对呈现阶段而不是useEffect的deps进行了优化,要检测值更新,您可能需要使用外部自定义挂钩进行值比较。
我怀疑(没有时间看代码) watch返回值是在每次呈现时创建的。那么每个呈现上的fieldData都是对一个新对象的引用。
大多数情况下,我依赖于onChange或onBlur表单事件。
function Form() {
const onChangeHandler = () => { /* ... */ };
return <form onChange={onChangeHandler}>
{/* ... */}
</form>
}然后我使用useForm().getValues函数来检索当前的表单值,但是您需要使用模式验证来在"autosave“函数有效时触发它。
另一种解决方案(也许是更简单的解决方案)是使用一个自定义钩子来深入比较这些值。您可以从useDeepCompareEffect的反应-使用中了解到这一点。
您的代码中还有另一个错误,即使用const debouncedFunction = debounce(myFunction, 500)不起作用。一个“脱节”的功能是回忆录。由于我们处于呈现函数(functional )中,回忆录函数将在每次呈现时创建,因此它将被调用,而不考虑您设置的阈值。
为此,您需要使用React.useMemo:
const { mutateAsync } = mutationResult;
const handleDebouncedChange = React.useMemo(
() =>
debounce((data: NameType) => {
mutateAsync(data);
}, 500),
[mutateAsync]
);一个完全工作的版本可在此作为代码框提供将是:
import React from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";
import debounce from "just-debounce-it";
type NameType = {
id: number;
firstName: string;
lastName: string;
};
const schemaValidation = Yup.object().shape({
id: Yup.number().required("Required"),
firstName: Yup.string().required("Required"),
lastName: Yup.string()
.required()
.min(2, "Must be greater than 1 character")
.max(50, "Must be less than 30 characters")
});
const getMockData = async () => {
const name: NameType = {
id: 1,
firstName: "John",
lastName: "Doe"
};
return await Promise.resolve(name);
};
const saveChangeToDatabase = async (args: NameType) => {
console.count("payload for patch:" + JSON.stringify(args));
return await Promise.resolve(args);
};
const NameForm = () => {
const queryResult = useQuery(["user"], getMockData);
const mutationResult = useMutation(saveChangeToDatabase, {
onSuccess: (nameToSave: NameType) => {
console.count("success mutating: " + JSON.stringify(nameToSave));
}
});
const { register, reset, watch, getValues, formState } = useForm<NameType>({
mode: "all",
criteriaMode: "all",
resolver: yupResolver(schemaValidation)
});
const { errors } = formState;
const fieldData = watch();
const { mutateAsync } = mutationResult;
const handleDebouncedChange = React.useMemo(
() =>
debounce((data: NameType) => {
mutateAsync(data);
}, 500),
[mutateAsync]
);
React.useEffect(() => {
reset(queryResult.data);
}, [queryResult.data]);
const onChange = async () => {
const data = getValues();
try {
console.log(formState);
const validated = await schemaValidation.validate(data);
handleDebouncedChange(validated);
} catch (e) {}
};
if (queryResult.isLoading) {
return <h2>Loading...</h2>;
}
return (
<form
style={{
display: "flex",
flexDirection: "column",
margin: "auto",
width: 300
}}
onChange={onChange}
>
<input {...register("firstName")} placeholder="First name" />
<div style={{ color: "red" }}>{errors && errors?.firstName?.message}</div>
<input {...register("lastName")} placeholder="Last name" />
<div style={{ color: "red" }}>{errors && errors?.lastName?.message}</div>
{"Field data: " + JSON.stringify(fieldData)}
</form>
);
};
export default NameForm;https://stackoverflow.com/questions/73614962
复制相似问题