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

React+Firestore:用户加入活动后如何重新获取费用数据?

问题描述

我有一个包含user、event和expense集合的Firestore数据库:

  • 每个event关联多名参与用户
  • 每个expense链接到特定活动

初始逻辑:用户访问非参与活动时,该活动的费用会被隐藏,显示“加入”按钮;用户点击按钮成为参与者后,event文档更新,用户获得该活动费用的访问权限。

使用React+Firestore开发时遇到的核心问题:用户加入活动后,由于expense集合本身未发生变更,费用不会自动显示,需要实现无需页面刷新就能重新获取expenses查询结果。

简化后的React代码:

function Event() {
  const [user] = useAuthState(fbAuth);
  const { eventId } = useParams();
  const [event, eventLoading] = useGetEvent(eventId as string);

  const eventData = event.data();

  const isUserParticipating = user && eventData?.participantsIds?.includes(user?.uid);

  const [expenses, loading, error] = useGetEventExpenses(eventId as string);

  useEffect(() => {
    // 如何在用户加入活动时触发重新获取expenses查询,让费用无需刷新页面就显示?
  }, [isUserParticipating]);

  // ...jsx
}

Firestore安全规则:

service cloud.firestore {
    match /databases/{database}/documents {
        // 仅允许已认证用户写入event集合
        match /event/{eventId} {
            allow read: if true;
            allow write: if request.auth != null;
        }

        match /user/{userId} {
            allow read: if true;
            allow write: if request.auth.uid == userId;
        }

        // 用户仅能读写自己参与的活动的费用
        match /expense/{expenseId} {
            allow create: if request.auth.uid in get(/databases/$(database)/documents/event/$(request.resource.data.parentEventId)).data.participantsIds;
            allow delete,read: if request.auth.uid in get(/databases/$(database)/documents/event/$(resource.data.parentEventId)).data.participantsIds;
        }

    }
}

核心疑问:当决定资源访问权限的其他文档(此处为event文档)发生变更时,如何更新Firestore快照监听器?

解决方案

方法1:让自定义钩子依赖参与状态,自动重启监听器

修改useGetEventExpenses钩子,将isUserParticipating作为依赖项。当参与状态变化时,自动取消旧监听器并创建新的查询监听器。

修改后的useGetEventExpenses示例:

function useGetEventExpenses(eventId, isUserParticipating) {
  const [expenses, setExpenses] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!eventId || !isUserParticipating) {
      setExpenses([]);
      setLoading(false);
      return;
    }

    // 创建费用查询监听器
    const unsubscribe = db.collection('expense')
      .where('parentEventId', '==', eventId)
      .onSnapshot(
        (snapshot) => {
          const expensesList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
          setExpenses(expensesList);
          setLoading(false);
        },
        (err) => {
          setError(err);
          setLoading(false);
        }
      );

    // 组件卸载或依赖变化时取消监听器
    return unsubscribe;
  }, [eventId, isUserParticipating]);

  return [expenses, loading, error];
}

在Event组件中调用时传入参与状态:

const [expenses, loading, error] = useGetEventExpenses(eventId as string, isUserParticipating);

当isUserParticipating从false变为true时,钩子会自动重新创建监听器,获取用户有权限访问的费用数据。

方法2:手动触发查询刷新(适配支持手动刷新的钩子)

如果useGetEventExpenses钩子可以暴露手动刷新方法,可在useEffect中直接调用:

首先修改钩子添加刷新函数:

function useGetEventExpenses(eventId) {
  const [expenses, setExpenses] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchExpenses = useCallback(async () => {
    setLoading(true);
    try {
      const snapshot = await db.collection('expense')
        .where('parentEventId', '==', eventId)
        .get();
      const expensesList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
      setExpenses(expensesList);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [eventId]);

  // 初始加载
  useEffect(() => {
    if (eventId) fetchExpenses();
  }, [eventId, fetchExpenses]);

  return [expenses, loading, error, fetchExpenses];
}

在Event组件中触发刷新:

const [expenses, loading, error, fetchExpenses] = useGetEventExpenses(eventId as string);

useEffect(() => {
  // 用户刚加入活动时,手动触发费用查询
  if (isUserParticipating) {
    fetchExpenses();
  }
}, [isUserParticipating, fetchExpenses]);

方法3:监听event文档变化,自动触发费用查询

利用用户加入活动时event文档会更新的特性,在useGetEventExpenses中同时监听event文档的变化,一旦participantsIds更新,就重新获取费用:

function useGetEventExpenses(eventId) {
  const [expenses, setExpenses] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [user] = useAuthState(fbAuth);

  const fetchExpenses = useCallback(async () => {
    if (!user) return;
    setLoading(true);
    try {
      const snapshot = await db.collection('expense')
        .where('parentEventId', '==', eventId)
        .get();
      const expensesList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
      setExpenses(expensesList);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [eventId, user]);

  useEffect(() => {
    if (!eventId || !user) return;

    // 监听event文档的变化
    const unsubscribeEvent = db.collection('event').doc(eventId)
      .onSnapshot(() => {
        // 每次event文档更新,重新获取费用
        fetchExpenses();
      });

    // 初始加载费用
    fetchExpenses();

    return () => unsubscribeEvent();
  }, [eventId, user, fetchExpenses]);

  return [expenses, loading, error];
}

这种方式无需依赖外部的isUserParticipating状态,直接通过监听event文档变化触发查询,更贴合Firestore的实时特性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 14:10:34