使用mutateAsync链式调用React Query mutations触发无限循环求助
尝试通过自定义hooks和mutateAsync链式调用两个mutations时,触发了React的过多重渲染错误,错误指向这两个mutations的调用。虽然数据已成功存入数据库,router.push也正常工作,但仍报错。
已知可行方案(但不想采用):将调用写在mutationFn中、在第一个mutation的onSuccess回调中调用第二个。希望保留mutateAsync以保持两个mutations分离。
调用代码
const { mutateAsync: mutateTenant, isError: tenantIsError, error: tenantError, } = useCreateTenant(router); const { data: customerId, mutateAsync: mutateCustomer, isSuccess, isError: customerIsError, error: customerError, } = useCreateCustomer(); if (isSuccess) { setState({ ...state, customerId }); } const onSubmit = async (values) => { await mutateCustomer(values); await mutateTenant(values); };
自定义hooks代码
export const useCreateCustomer = () => { const mutation = useMutation({ mutationFn: (values) => createCustomer(values), }); return mutation; }; export const useCreateTenant = (router: NextRouter) => { const mutation = useMutation({ mutationFn: (values) => createTenant(values), onSuccess: (data) => { successToast(data.message); router.push('/register/plan'); }, }); return mutation; };
错误根源
问题出在这段直接在组件渲染流程中执行的state更新代码:
if (isSuccess) { setState({ ...state, customerId }); }
当useCreateCustomer的isSuccess变为true时,会立即调用setState触发组件重渲染。但React Query的isSuccess状态不会自动重置(除非触发新的mutation),导致每次重渲染时isSuccess仍为true,进而再次执行setState,形成无限重渲染循环,最终触发React的过多重渲染错误。
修复方案
方案1:用useEffect监听状态变化
把state更新逻辑放到useEffect中,仅在isSuccess和customerId变化时执行:
useEffect(() => { if (isSuccess && customerId) { setState(prev => ({ ...prev, customerId })); } }, [isSuccess, customerId]);
这样只会在isSuccess从false变为true时执行一次state更新,不会触发循环。
方案2:mutation完成后手动重置状态
在useEffect中更新state后,调用React Query提供的reset方法重置mutation状态,避免后续重渲染时isSuccess仍为true:
const { data: customerId, mutateAsync: mutateCustomer, isSuccess, reset, // 解构出reset方法 isError: customerIsError, error: customerError, } = useCreateCustomer(); useEffect(() => { if (isSuccess && customerId) { setState(prev => ({ ...prev, customerId })); reset(); // 重置mutation状态 } }, [isSuccess, customerId, reset]);
方案3:React Query v5+ 禁用自动success状态
如果使用React Query v5及以上版本,可以在调用mutateAsync时传入{ success: false },禁止自动设置isSuccess状态,直接用返回的数据更新state:
const onSubmit = async (values) => { const customerData = await mutateCustomer(values, { success: false }); setState(prev => ({ ...prev, customerId: customerData.id })); await mutateTenant(values); };
这种方式完全不依赖isSuccess状态,从根源避免循环问题。
内容的提问来源于stack exchange,提问作者Tibo

