如何在React Hook Form提交时触发React Query并传递参数至查询键
解决方案:React Query + React Hook Form 按需触发查询并缓存
核心问题拆解
- 实时表单值导致意外查询:直接将
getValues()放入query key,每次表单输入(尤其是Autocomplete频繁更新状态)会触发组件重渲染,getValues()返回新对象导致query key变化,React Query误以为是新查询自动触发请求。 - 自动查询逻辑冲突:原
enabled依赖watch("users"),用户选择时会实时更新enabled状态,触发不必要的查询。 - 提交重复触发:提交时手动调用
refetch(),同时query key变化可能触发React Query自动查询,导致两次请求。
针对性修改方案
1. 解耦查询参数与实时表单状态
新增独立状态存储提交时的参数快照,仅在点击提交时更新,避免实时表单值干扰query key。
2. 彻底禁用自动查询
将useQuery的enabled设为false,完全由提交动作控制查询触发时机。
3. 监听参数变化触发查询
用useEffect监听提交参数的变化,仅在参数更新时执行查询,确保单次触发。
修改后的完整代码
DetailedReport 组件
export function DetailedReport() { const defaultStartDate = dayjs().startOf("month").subtract(1, "month"); const defaultEndDate = dayjs().endOf("month"); // 定义默认参数,同时作为表单初始值和查询参数初始值 const defaultParams = { start_date: defaultStartDate, end_date: defaultEndDate, note: "", users: [], works: [], }; const { handleSubmit, control, setValue } = useForm({ defaultValues: defaultParams, }); // 存储提交后的查询参数(仅在提交时更新) const [searchParams, setSearchParams] = useState(defaultParams); const { data: reports, isLoading, refetch } = useQuery({ queryKey: ["reports", "detailed", searchParams], queryFn: () => loadDetailedActivity(searchParams), enabled: false, // 禁用自动查询,完全由提交控制 }); const onSubmit = (formData) => { // 转换日期为可序列化字符串(避免dayjs对象导致query key不稳定) const serializedParams = { ...formData, start_date: formData.start_date.toISOString(), end_date: formData.end_date.toISOString(), }; setSearchParams(serializedParams); }; // 仅在查询参数更新时触发查询 useEffect(() => { // 保留原逻辑:用户列表非空才查询 if (searchParams.users.length > 0) { refetch(); } }, [searchParams, refetch]); return ( <div className="flex flex-col gap-5"> <div className="text-3xl font-bold text-slate-600">Rapport détaillé</div> <Paper elevation={2} className="mb-5 flex flex-col gap-5 p-5"> <form onSubmit={handleSubmit(onSubmit)}> <Form isLoading={isLoading} control={control} setValue={setValue} /> </form> <div><Reports reports={reports} /></div> </Paper> </div> ); }
Form 组件(无需大幅修改,仅保留原有逻辑)
export function Form({ isLoading, control, setValue }) { const { managedUsers } = useGetMyManagedUsers({ enabled: true }); const { users, isLoading: isLoadingUsers } = useGetUsers({ enabled: true }); useEffect(() => { if (managedUsers) setValue("users", managedUsers); }, [managedUsers, setValue]); return ( <div className="flex flex-col gap-5"> <div className="flex items-center justify-start gap-5"> <div> <Button type="submit" size="large" disabled={isLoading} variant="contained" > {isLoading ? ( <FontAwesomeIcon className="fa-spin m-auto text-xl" icon={faSpinner} /> ) : ( "Envoyer" )} </Button> </div> </div> <div style={{ minWidth: "315px" }} className="flex"> {isLoadingUsers ? ( <Skeleton variant="rounded" width={"315px"} height={"56px"} /> ) : ( <div> <Controller name="users" control={control} render={({ field: { value, onChange } }) => ( <Autocomplete options={ hasPermission("VIEW_OTHERS_TIME_WORK") ? users : managedUsers } value={value} limitTags={3} multiple onChange={(_, data) => onChange(data)} getOptionLabel={(option) => option?.fullName} isOptionEqualToValue={(option, value) => option.id === value.id} filterSelectedOptions renderInput={(params) => ( <TextField {...params} label={"Utilisateurs"} sx={{ "& .MuiFormHelperText-root.Mui-error": { position: "absolute", top: "100%", }, }} /> )} /> )} /> </div> )} </div> </div> ); }
关键修改说明
- 查询参数快照:
searchParams仅在提交时更新,确保query key稳定,相同参数自动命中缓存。 - 禁用自动查询:
enabled: false彻底关闭React Query的自动触发逻辑,避免表单输入时的意外请求。 - 单次查询触发:通过
useEffect监听searchParams变化,仅在参数更新时执行一次refetch(),解决提交两次的问题。 - 序列化日期:将dayjs对象转为ISO字符串,确保query key可序列化,避免缓存失效。
内容的提问来源于stack exchange,提问作者Enzo
相关产品推荐
相关产品推荐

