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

如何在React Hook Form提交时触发React Query并传递参数至查询键

解决方案:React Query + React Hook Form 按需触发查询并缓存

核心问题拆解

  1. 实时表单值导致意外查询:直接将getValues()放入query key,每次表单输入(尤其是Autocomplete频繁更新状态)会触发组件重渲染,getValues()返回新对象导致query key变化,React Query误以为是新查询自动触发请求。
  2. 自动查询逻辑冲突:原enabled依赖watch("users"),用户选择时会实时更新enabled状态,触发不必要的查询。
  3. 提交重复触发:提交时手动调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 22:15:32