Reactfire 不显示在 Firestore 中进行的实时更新

Reactfire not showing live updates made in Firestore

我正在努力思考 Firestore/Reactfire 所以我正在开发一个基本的待办事项应用程序来帮助我掌握这些概念。

我有一个屏幕 /Screens/Mainscreen.js 可以加载用户的所有待办事项列表。顶部有一个输入和一个用于添加新列表的按钮。这一切都有效,除了新列表没有加载到屏幕上?如果我刷新屏幕,则会加载新列表。似乎我错过了 Reactfire 工作原理的一部分,但我的代码似乎遵循所有演示文档,所以我不确定我做错了什么。

此处是完整的 WIP 项目:https://github.com/warm--tape/todo/(如果需要,添加带有 Firebase 凭据的 .env)

这是我的 MainScreen.js:

function MainScreen({ navigation }) {

  // Logout action. Probably move.
  const auth = useAuth();
  function handleLogOut() {
    auth.signOut().then(() => navigation.replace('AuthScreen'));
  }

  // Load User
  const { status: userStatus, data: user } = useUser();
  const { uid } = user;

  // Load User Lists from Firestore
  const firestore = useFirestore();
  const listCollection = collection(firestore, 'lists');
  const userListQuery = query(listCollection, where('access', 'array-contains', uid || 0));
  const { status: listStatus, data: rawListData } = useFirestoreCollectionData(userListQuery, { idField: 'id' });

  // Load toast
  const toast = useToast();

  // Set up state  
  const [isLoading, setIsLoading] = useState(true);
  const [listData, setListData] = useState([]);

  const [listToAdd, setListToAdd] = useState({ listName: '' });
  const [itemToAdd, setItemToAdd] = useState({ itemName: '' });
  const [errors, setErrors] = useState({});

  // Hide screen until loaded
  if (isLoading && userStatus === 'success' && listStatus === 'success') {
    setListData(rawListData);
    setIsLoading(false);
  }

  // Form Validator
  const validate = () => {
    // Currently no validation
    return true;
  };

  // Handle add list
  async function onAddList() {
    if (validate()) {
      delete listToAdd.NO_ID_FIELD;
      const listToAddData = { ...listToAdd, ...{ owner: uid, access: [uid] } };
      await addDoc(collection(firestore, "lists"), listToAddData).then(() => {
        toast.show({
          title: "List Added",
          placement: "bottom"
        });
        setListToAdd({ listName: '' });
      });
    } else {
      alert('Validation Failed')
    }
  };

  // Handle press on list
  function onPressListHandler(id) {
    navigation.navigate('ListDetailScreen', {listId: id})
  }

  // =========================================================================
  // Render loading spinner

  if (isLoading) {
    return (
      <LoadingSpinner />
    );
  }

  // =========================================================================
  // Render

  const listRenderItem = ({ item }) => (
    <Pressable
      onPress={()=>{onPressListHandler(item.id)}}
      borderBottomWidth="1" _dark={{borderColor: "gray.600"}} borderColor="coolGray.200" pl="4" pr="5" py="2"
    >
      <HStack space={3} justifyContent="start" alignItems="center">
        <IconButton variant="unstyled" icon={<Icon as={Ionicons} name="list-outline" size="sm" />} onPress={() => {}} />
        <Text _dark={{color: "warmGray.50"}} color="coolGray.800">
          {item.listName}
        </Text>
      </HStack>
    </Pressable>
  );


  // =========================================================================
  // Render

  return (
    <ScreenWrapper>

      <HStack>
        <FormControl isRequired isInvalid={'listName' in errors}>
          <Input placeholder="Add List..." value={listToAdd.listName} onChangeText={value => setListToAdd({ ...listToAdd, ...{ listName: value } })} />
          {'listName' in errors ? <FormControl.ErrorMessage>{errors.listName}</FormControl.ErrorMessage> : null}
        </FormControl>
        <Button onPress={() => { onAddList() }} >
          <Icon color="white" as={Ionicons} name="add" size="sm" />
        </Button>
      </HStack>

      <Divider my="3" />

      <Box>
        <FlatList
          data={listData}
          renderItem={listRenderItem}
          keyExtractor={item => item.id} />
      </Box>

      <Divider my="3" />

      <Button onPress={() => handleLogOut()}>Logout</Button>

    </ScreenWrapper>
  );
}

export default MainScreen;

原来我的代码结构很糟糕,所以在深入查看了一些演示和文档之后,以下重构有效:

import React, { useState } from 'react';
import { useFirestore, useUser, useFirestoreCollectionData } from 'reactfire';
import { addDoc, collection, query, where } from 'firebase/firestore';

import {
  Text,
  Button,
  FlatList,
  HStack,
  Pressable,
  Icon,
  FormControl,
  Input,
  IconButton,
  useToast,
  Divider
} from 'native-base';
import { Ionicons } from '@expo/vector-icons';
import ScreenWrapper from '../components/ScreenWrapper';
import LoadingSpinner from '../components/LoadingSpinner';

function onPressListHandler(id) {
  navigation.navigate('ListDetailScreen', { listId: id })
}

const listRenderItem = ({ item }) => (
  <Pressable
    onPress={() => { onPressListHandler(item.id) }}
    borderBottomWidth="1" _dark={{ borderColor: "gray.600" }} borderColor="coolGray.200" pl="4" pr="5" py="2"
  >
    <HStack space={3} justifyContent="start" alignItems="center">
      <IconButton variant="unstyled" icon={<Icon as={Ionicons} name="list-outline" size="sm" />} onPress={() => { }} />
      <Text _dark={{ color: "warmGray.50" }} color="coolGray.800">
        {item.listName}
      </Text>
    </HStack>
  </Pressable>
);

const ListsList = () => {

  // Load User
  const { status: userStatus, data: user } = useUser();
  const { uid } = user;

  // Load User Lists from Firestore
  const firestore = useFirestore();
  const listCollection = collection(firestore, 'lists');
  const userListQuery = query(listCollection, where('access', 'array-contains', uid || 0));
  const { status: listStatus, data: lists } = useFirestoreCollectionData(userListQuery, { idField: 'id' });

  // Loading lists
  if (userStatus === 'loading' || listStatus === 'loading') {
    return <LoadingSpinner />;
  }

  // Render
  return (
    <>
      <FlatList
        data={lists}
        renderItem={listRenderItem}
        keyExtractor={item => item.id}
      />
    </>
  );
};

function MainScreen({ navigation }) {

  // Load toast
  const toast = useToast();

  const [listToAdd, setListToAdd] = useState({ listName: '' });
  const [errors, setErrors] = useState({});

  // Form Validator
  const validate = () => {
    // None needed here.
    return true;
  };

  // Handle add list
  async function onAddList() {
    if (validate()) {
      delete listToAdd.NO_ID_FIELD;
      const listToAddData = { ...listToAdd, ...{ owner: uid, access: [uid] } };
      await addDoc(collection(firestore, "lists"), listToAddData).then(() => {
        toast.show({
          title: "List Added",
          placement: "bottom"
        });
        setListToAdd({ listName: '' });
      });
    } else {
      alert('Validation Failed')
    }
  };


  // =========================================================================
  // Render

  return (
    <ScreenWrapper>

      <HStack>
        <FormControl isRequired isInvalid={'listName' in errors}>
          <Input placeholder="Add List..." value={listToAdd.listName} onChangeText={value => setListToAdd({ ...listToAdd, ...{ listName: value } })} />
          {'listName' in errors ? <FormControl.ErrorMessage>{errors.listName}</FormControl.ErrorMessage> : null}
        </FormControl>
        <Button onPress={() => { onAddList() }} >
          <Icon color="white" as={Ionicons} name="add" size="sm" />
        </Button>
      </HStack>

      <Divider my="3" />

      <ListsList />

    </ScreenWrapper>
  );
}

export default MainScreen;