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

React useCollection自定义Hook重渲染后参数未更新问题

问题成因

核心bug出在useCollection自定义Hook中useRef的错误使用:

  • 你最初添加useRef的目的,是解决「每次组件渲染都会新建查询条件数组,数组引用地址变化导致useEffect无限循环触发」的问题,这个思路方向是对的,但实现逻辑完全写反了:const query = useRef(_query).current这类写法,只会在Hook首次挂载时把初始传入的_query参数存到ref对象中,后续组件重渲染时,哪怕传入的_query已经是更新后的新值,useRef也不会主动更新已存储的current值,最终你拿到的query、query2、query3永远是组件第一次挂载时的初始查询条件。
  • 当你提交日期筛选、调用setDateStart/setDateEnd触发组件重渲染时,新的日期范围参数确实传到了Hook里,但Hook内部直接把新值忽略了。useEffect的依赖项全是存了初始值的旧变量,根本不会感知到参数变化,自然不会重新执行Firebase数据查询,也就拿不到筛选后的结果。
修复方案

推荐用第一种方案,改动小、兼容性好,不需要修改所有调用Hook的组件代码。

方案1:修正useRef的更新逻辑(推荐)

核心调整是:不要直接把ref初始值的current取出来当常量用,而是在每次渲染时对比传入的新参数和ref中存储的旧参数,只有参数内容真的发生变化时,才更新ref的存储值,同时加入简单的深度判断,避免数组引用变化导致的无限循环。
修改后的useCollection完整代码:

import { useEffect, useState, useRef } from "react";
import { projectFirestore } from "../firebase/config";

// 简单深度比较,适配当前查询参数都是基本类型、Date对象的场景
const isValueEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);

export const useCollection = (
  collection,
  _query,
  _query2,
  _query3,
  _orderBy
) => {
  const [documents, setDocuments] = useState(null);
  const [error, setError] = useState(null);

  // 初始化ref存储查询参数
  const queryRef = useRef(_query);
  const query2Ref = useRef(_query2);
  const query3Ref = useRef(_query3);
  const orderByRef = useRef(_orderBy);

  // 每次渲染对比新旧参数,内容真的变化时才更新ref存储值
  if (!isValueEqual(queryRef.current, _query)) queryRef.current = _query;
  if (!isValueEqual(query2Ref.current, _query2)) query2Ref.current = _query2;
  if (!isValueEqual(query3Ref.current, _query3)) query3Ref.current = _query3;
  if (!isValueEqual(orderByRef.current, _orderBy)) orderByRef.current = _orderBy;

  useEffect(() => {
    let ref = projectFirestore.collection(collection);

    if (queryRef.current) ref = ref.where(...queryRef.current);
    if (query2Ref.current) ref = ref.where(...query2Ref.current);
    if (query3Ref.current) ref = ref.where(...query3Ref.current);
    if (orderByRef.current) ref = ref.orderBy(...orderByRef.current);

    const unsubscribe = ref.onSnapshot(
      (snapshot) => {
        const results = snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }));
        setDocuments(results);
        setError(null);
      },
      (error) => {
        console.log(error);
        setError("could not fetch the data");
      }
    );

    return () => unsubscribe();
  }, [collection, queryRef, query2Ref, query3Ref, orderByRef]);

  return { documents, error };
};

改完之后不需要修改TimeCard组件的任何调用代码,日期筛选提交后Hook就能拿到最新的查询参数,自动重新拉取对应范围的数据。

方案2:组件侧稳定查询参数引用

如果不想修改Hook的公共逻辑,也可以在TimeCard组件侧用useMemo包裹所有查询条件数组,只有当依赖的状态真的变化时才生成新数组,同时删掉Hook里错误的ref固定值逻辑:

  1. 修改TimeCard中调用useCollection的部分:
import React, { useState, useEffect, useMemo } from "react";
// ...其他导入代码不变

const TimeCard = () => {
  // ...原有状态逻辑不变
  const { uid } = useAuthContext().user;

  // 用useMemo稳定查询数组的引用
  const baseQuery = useMemo(() => ["uid", "==", uid], [uid]);
  const startQuery = useMemo(() => ["createdAt", ">=", dateStart], [dateStart]);
  const endQuery = useMemo(() => ["createdAt", "<=", dateEnd], [dateEnd]);
  const sortRule = useMemo(() => ["createdAt", "asc"], []);

  const { documents } = useCollection(
    "timeinout",
    baseQuery,
    startQuery,
    endQuery,
    sortRule
  );

  // ...原有其他逻辑不变
}
  1. 修改useCollection代码,删掉所有useRef相关逻辑,直接使用传入的参数作为useEffect依赖:
export const useCollection = (
  collection,
  query,
  query2,
  query3,
  orderBy
) => {
  const [documents, setDocuments] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    let ref = projectFirestore.collection(collection);

    if (query) ref = ref.where(...query);
    if (query2) ref = ref.where(...query2);
    if (query3) ref = ref.where(...query3);
    if (orderBy) ref = ref.orderBy(...orderBy);

    const unsubscribe = ref.onSnapshot(
      (snapshot) => {
        const results = snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }));
        setDocuments(results);
        setError(null);
      },
      (error) => {
        console.log(error);
        setError("could not fetch the data");
      }
    );

    return () => unsubscribe();
  }, [collection, query, query2, query3, orderBy]);

  return { documents, error };
};

注意:这个方案要求所有调用useCollection的业务组件,都必须自己保证传入的数组、对象类型参数引用稳定,否则还是会触发useEffect无限循环,维护成本比方案1高。

验证方式

修改完成后提交日期筛选,控制台打印的查询参数会同步成你选择的最新日期,Firebase会自动重新拉取对应时间范围的数据,表格展示内容随筛选条件实时更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:48:58