Jar*_*red 4 javascript reactjs react-hook-form react-query
我正在尝试使用 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",
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我还在这里制作了一个 create-react-app 复制品。您可以克隆存储库,运行 npm i 、 npm start ,当您更改表单时您就会看到问题。这是您需要查看的唯一页面:
https://github.com/k-38/react-query_react-hook-form_autosave/blob/main/src/NameForm.tsx
Run Code Online (Sandbox Code Playgroud)
任何帮助表示感谢,谢谢
更新:非常感谢您的回答。我接受了你的答案,并在自定义挂钩中抽象出了许多去抖/回调逻辑(尚未输入):
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
}
}
Run Code Online (Sandbox Code Playgroud)
因此,功能组件中的无限循环useEffect
通常是由于每个“循环周期”上的依赖值发生突变所致。
在文档中我们可以读到:
watch 结果针对渲染阶段而不是 useEffect 的 deps 进行了优化,为了检测值更新,您可能需要使用外部自定义挂钩进行值比较。
我怀疑(没有时间查看代码)watch
每个渲染都会创建返回值。然后fieldData
在每个渲染上都是对新对象的引用。
大多数时候,我依赖onChange
或onBlur
形成事件。
function Form() {
const onChangeHandler = () => { /* ... */ };
return <form onChange={onChangeHandler}>
{/* ... */}
</form>
}
Run Code Online (Sandbox Code Playgroud)
然后我使用useForm().getValues
函数来检索当前表单值,但您需要使用模式验证来在有效时触发“自动保存”函数。
另一个解决方案(也许是一个更简单的解决方案):是使用自定义挂钩来深入比较这些值。useDeepCompareEffect
您可以从react-use中查看一下。
您的代码中的 debounce 函数还有另一个错误:使用
const debouncedFunction = debounce(myFunction, 500)
不起作用。“去抖”函数被记忆。由于我们处于渲染函数(功能组件)中,因此将在每次渲染时创建记忆函数,因此无论您设置的阈值如何,都会调用它。
React.useMemo
为此,您需要使用:
const { mutateAsync } = mutationResult;
const handleDebouncedChange = React.useMemo(
() =>
debounce((data: NameType) => {
mutateAsync(data);
}, 500),
[mutateAsync]
);
Run Code Online (Sandbox Code Playgroud)
此处提供的完整工作版本为codesandbox :
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;
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
4316 次 |
最近记录: |