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

React Native Firestore:如何结合.where()与.onSnapshot()监听数据更新?

问题:Firestore结合where查询监听数据变更,FlatList不更新且重复key错误

问题背景

我实现了一个从Firestore数据库加载数据的FlatList,目前可执行所有CRUD操作,但编辑条目后,FlatList未同步更新(数据库中数据已变更)。怀疑原因是未使用.onSnapshot(),但不清楚如何将.where()查询与监听数据变更结合。

原代码如下:

export const Coach = () => {
  const navigation = useNavigation();
  const [user, setUser] = useState();
  const [userName, setUserName] = useState('');
  const [workoutIds, setWorkoutIds] = useState([]);
  const [workouts, setWorkouts] = useState([]);
  const userRef = firestore().collection('Users');
  const workoutRef = firestore().collection('Workouts');

  // Setting the user state
  auth().onAuthStateChanged(userInstance => {
    if (userInstance) {
      setUser(userInstance);
    }
  });

  // Getting coach id's from firestore - Started out at individual workout id's
  useEffect(() => {
    if (user) {
      const subscriber = userRef.doc(user.uid).onSnapshot(userSnap => {
        if (userSnap) {
          setUserName(userSnap.data().Name);
          setWorkoutIds(userSnap.data().Workouts);
        }
      });
      return () => subscriber();
    }
  }, [user]);

  // using the list of coach id's to get workouts
  useEffect(() => {
    if (workoutIds.length != 0) {
      let workoutList = [];
      workoutRef
        .where(firestore.FieldPath.documentId(), 'in', workoutIds)
        .get()
        .then(query => {
          query.forEach(snap => {
            workoutList.push({...snap.data(), key: snap.id});
          });
          setWorkouts(workoutList);
        });
    }
  }, [workoutIds]);

我尝试在查询中添加.onSnapshot(),修改前后代码对比:

修改前

// using the list of coach id's to get workouts
useEffect(() => {
  if (workoutIds.length != 0) {
    let workoutList = [];
    workoutRef
      .where(firestore.FieldPath.documentId(), 'in', workoutIds)
      .get()
      .then(query => {
        query.forEach(snap => {
          workoutList.push({...snap.data(), key: snap.id});
        });
        setWorkouts(workoutList);
      });
  }
}, [workoutIds]);

修改后

// using the list of coach id's to get workouts
useEffect(() => {
  if (workoutIds.length != 0) {
    let workoutList = [];
    workoutRef
      .where(firestore.FieldPath.documentId(), 'in', workoutIds)
      .onSnapshot(query => {
        query.forEach(snap => {
          workoutList.push({...snap.data(), key: snap.id});
        });
        setWorkouts(workoutList);
      });
  }
}, [workoutIds]);

但视图仍无法即时更新,且出现重复key错误。


修复方案

问题根源

  1. 重复key错误:每次onSnapshot触发时,workoutList没有重置,旧数据持续累加,导致同一key多次出现。
  2. 监听器未清理:useEffect未返回订阅取消函数,当workoutIds变化时,旧的监听器仍在运行,会引发重复更新和内存泄漏。
  3. 数据更新逻辑冗余:用forEach+push的方式构建数组容易出错,改用map更简洁可靠。

修复后的代码

export const Coach = () => {
  const navigation = useNavigation();
  const [user, setUser] = useState();
  const [userName, setUserName] = useState('');
  const [workoutIds, setWorkoutIds] = useState([]);
  const [workouts, setWorkouts] = useState([]);
  const userRef = firestore().collection('Users');
  const workoutRef = firestore().collection('Workouts');

  auth().onAuthStateChanged(userInstance => {
    if (userInstance) {
      setUser(userInstance);
    }
  });

  useEffect(() => {
    if (user) {
      const subscriber = userRef.doc(user.uid).onSnapshot(userSnap => {
        if (userSnap.exists) {
          const data = userSnap.data();
          setUserName(data.Name);
          setWorkoutIds(data.Workouts || []);
        }
      });
      return () => subscriber();
    }
  }, [user]);

  useEffect(() => {
    // 当workoutIds为空时直接清空列表
    if (workoutIds.length === 0) {
      setWorkouts([]);
      return;
    }

    // 创建快照订阅
    const subscriber = workoutRef
      .where(firestore.FieldPath.documentId(), 'in', workoutIds)
      .onSnapshot(querySnapshot => {
        // 每次快照触发时重新构建数组,避免累加
        const updatedWorkouts = querySnapshot.docs.map(snap => ({
          ...snap.data(),
          key: snap.id
        }));
        setWorkouts(updatedWorkouts);
      });

    // 返回取消订阅函数,清理旧监听器
    return () => subscriber();
  }, [workoutIds]);

  // ... 其他组件逻辑(比如FlatList渲染)
};

关键修改点

  • 重置数据数组:用querySnapshot.docs.map直接生成新数组,替代push累加,确保每次快照都是全新数据。
  • 清理监听器:在useEffect中返回订阅的取消函数,当组件卸载或workoutIds变化时,自动取消旧的监听,避免重复更新。
  • 空值处理:增加workoutIds为空时的处理逻辑,直接清空列表,避免不必要的查询。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 18:48:24