You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Query结合React-Hook-Form防抖自动保存遇无限循环问题求助

问题:React-Hook-Form + React-Query 防抖自动保存出现无限循环

我正尝试用React-Hook-Form和React-Query实现一个表单,用户修改任意字段时触发防抖自动保存。目前功能接近完成,但执行mutation时出现了无限循环问题。

依赖版本

"@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

复现方式

已基于create-react-app创建复现项目,克隆后执行npm i、npm start,修改表单即可触发无限循环问题。


更新:抽象自定义Hook

感谢解答,我已根据答案抽象出一个自定义Hook(暂未添加类型):

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
    }
}

内容的提问来源于stack exchange,提问作者Jar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 21:25:21