【发布时间】:2020-10-17 15:48:34
【问题描述】:
我有一个将数据存储在 Cloud Firestore 中的 Electron/React 应用程序。我正在使用 Firebase Node.JS SDK。我的 Firestore 数据结构中有 3 个集合,如下所示:类别 >(类别集合)> 子类别 >(子类别集合)> 项目 >(项目集合)。我在组件的 componentDidMount 方法中向根类别集合添加了一个侦听器,该方法正在运行。我要做的是在用户单击类别时将侦听器添加到特定的子类别集合中。我为子类别侦听器重复了与类别侦听器相同的代码,但是即使根类别集合上的侦听器几乎立即触发,子类别侦听器也需要 15-20 秒才能触发。我已经用console.log 语句验证了onClick 处理程序在单击类别后立即被调用,但是onSnapshot 块内的代码直到单击类别后整整15-20 秒才会运行。此外,每个子类别集合的文档都比类别集合少,所以我知道这不是加载问题。我的代码在某些方面有问题吗?如果没有,有没有办法强制 Firestore 集合侦听器在初始化后立即触发?仅在根集合上使用侦听器并手动管理子集合的获取会更好吗?这是我的代码-
在AllInventory.tsx(我们的根组件):
import React, { Component } from 'react';
import MaterialList from './MaterialList';
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import { firebaseConfig } from './PrivateKeys';
firebase.initializeApp(firebaseConfig);
//I have tried swapping out any "firestore.something" statements with
//"firebase.firestore().something" statements but that didn't seem to make a difference
const firestore = firebase.firestore();
let categoriesListener = () => {};
let subcategoriesListener = () => {};
let itemsListener = () => {};
export default class AllInventory extends Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
categoriesArr: [],
subcategoriesArr: [],
itemsArr: [],
selectedCategory: "",
selectedSubcategory: "",
selectedItems: "",
};
}
componentDidMount() {
//This listener works and fires immediately
categoriesListener = firestore.collection("Categories").onSnapshot(querySnapshot => {
const categories: { name: string }[] = [];
querySnapshot.docChanges().forEach(change => {
if (change.type === 'added') {
categories.push({name: change.doc.id.toString()});
}
})
this.setState({categoriesArr: categories});
});
}
componentWillUnmount() {
//Remove all Firestore listeners
categoriesListener();
subcategoriesListener();
itemsListener();
}
handleCategoryClicked = (category: string) => {
console.log("This line executes right when the list item is clicked");
//Remove current listener, if it exists
subcategoriesListener();
//Add new listener
subcategoriesListener = firestore.collection("Categories").doc(category).collection("Subcategories").onSnapshot(querySnapshot => {
console.log("This line takes 15-20 seconds to execute after the function is called");
const subcategories: { name: string }[] = [];
querySnapshot.docChanges().forEach(change => {
if (change.type === 'added') {
subcategories.push({name: change.doc.id.toString()});
}
})
this.setState({
selectedCategory: category,
subcategoriesArr: subcategories
});
});
}
render() {
const {
categoriesArr,
subcategoriesArr,
itemsArr
} = this.state;
return (
<div className="flex-box-horizontal-base-div">
<div className="hRow stretchV">
<MaterialList {...this.state} contentType="categories" onItemClick={this.handleCategoryClicked}/>
</div>
<div className="hRow stretchV">
<MaterialList {...this.state} contentType="subcategories"/>
</div>
<div className="hRow stretchV last">
<MaterialList {...this.state} contentType="items"/>
</div>
</div>
);
}
}
在MaterialList.tsx:
import React from 'react';
import ListItem, { ListItemProps } from '@material-ui/core/ListItem';
import MenuItem from '@material-ui/core/MenuItem';
import List from '@material-ui/core/List';
import ListItemText from '@material-ui/core/ListItemText';
import Divider from '@material-ui/core/Divider';
//Theme and useStyles are only for changing the default font/color scheme for Material UI,
//so I haven't included them here as they are long functions
function MaterialListItem(props: any) {
const classes = useStyles();
const { name } = props;
return (
<div>
<MenuItem style={{whiteSpace: 'normal'}} onClick={() => props.onItemClick(name)}>
<ListItemText primary={name}/>
</MenuItem>
<Divider className={classes.dividerColor}/>
</div>
);
}
export default function MaterialList(props: any) {
const classes = useStyles();
const { contentType } = props;
switch (contentType) {
case "categories":
if (props.categoriesArr.length !== 0) {
return (
<ThemeProvider theme={theme}>
<List className={classes.root} component="nav">
<Divider className={classes.dividerColor}/>
{props.categoriesArr.map((item: { name: string }) => (
<MaterialListItem name={item.name} key={item.name} onItemClick={props.onItemClick}/>
))}
</List>
</ThemeProvider>
);
} else {
return (
<ThemeProvider theme={theme}>
<List className={classes.root} component="nav"/>
</ThemeProvider>
)
}
case "subcategories":
if (props.subcategoriesArr.length !== 0) {
return (
<ThemeProvider theme={theme}>
<List className={classes.root} component="nav">
<Divider className={classes.dividerColor}/>
{props.subcategoriesArr.map((item: { name: string }) => (
<MaterialListItem name={item.name} key={item.name}/>
))}
</List>
</ThemeProvider>
);
} else {
return (
<ThemeProvider theme={theme}>
<List className={classes.root} component="nav"/>
</ThemeProvider>
)
}
case "items":
if (props.itemsArr.length !== 0) {
return (
<ThemeProvider theme={theme}>
<List className={classes.root} component="nav">
<Divider className={classes.dividerColor}/>
{props.itemsArr.map((item: { name: string }) => (
<MaterialListItem name={item.name} key={item.name}/>
))}
</List>
</ThemeProvider>
);
} else {
return (
<ThemeProvider theme={theme}>
<List className={classes.root} component="nav"/>
</ThemeProvider>
)
}
default:
return (
<ThemeProvider theme={theme}>
<List className={classes.root} component="nav"/>
</ThemeProvider>
)
}
}
【问题讨论】:
标签: javascript reactjs typescript firebase google-cloud-firestore