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;
我正在努力思考 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;