我已经玩了一段时间了。我正在从Firebase获取数据并将该数据中的对象列表填充到此UserProfile页面。我只是想让它工作,这样当我进入个人资料页面时,会列出他们的项目列表。
问题是,对于此版本的代码,我必须单击两次配置文件链接才能显示项目,但显示名称显示正常。我知道setState是异步的。我已经尝试在setState中的回调中设置状态。我已经尝试过预先检查快照是否存在。我已经尝试过componentDidMount和componentDidUpdate。这些东西都没有帮助,我只是最终得到this.state.items.map无法调用或newState是一些空洞的,奇怪的事情。我现在不知道我哪里出错了。
当我对它进行调试时,看起来当从到目前为止从Firebase中取出任何内容时调用了setState,并且以后永远不会调用它。
我设置newState的方式可能有问题,因为当我尝试首次设置时,我在控制台日志时newState为空。我只是不知道何时在第一次渲染的适当时间设置它。
class UserProfile extends Component {
constructor(props){
super(props)
this.state = {
redirect: false,
userId: this.props.match.params.id,
displayName: "",
items: []
}
this.userRef = firebaseDB.database().ref(`/Users/${this.state.userId}`)
this.usersItemsRef = firebaseDB.database().ref(`/Users/${this.state.userId}/items`)
}
componentWillMount() {
this.userRef.on('value', snapshot => {
this.setState({
displayName: snapshot.val().displayName
});
});
this.usersItemsRef.on('value', snapshot => {
let usersItems = snapshot.val();
let newState = [];
for (let userItem in usersItems) {
var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
itemRef.once('value', snapshot => {
var item = snapshot.val()
newState.push({
id: itemRef.key,
title: item.title
});
})
}
this.setState({
items: newState
})
});
}
componentWillUnmount() {
this.userRef.off();
this.usersItemsRef.off();
}
render() {
return (
<div style={{marginTop: "100px"}}>
<h1>{this.state.displayName}</h1>
<Link className="pt-button" aria-label="Log Out" to={"/submit_item/"+this.state.userId} >Submit an item</Link>
<section className='display-itemss'>
<div className="wrapper">
<ul>
{this.state.items.map((item) => {
return (
<li key={item.id}>
<h3>{item.title}</h3>
</li>
)
})}
</ul>
</div>
</section>
</div>
);
}
}
答案 0 :(得分:3)
数据是从Firebase异步加载的,因为它可能需要一段不确定的时间。当数据可用时,Firebase会调用您传入的回调函数。但到那时您对componentWillMount() {
this.userRef.on('value', snapshot => {
this.setState({
displayName: snapshot.val().displayName
});
});
this.usersItemsRef.on('value', snapshot => {
let usersItems = snapshot.val();
let newState = [];
console.log("Before attaching once listeners");
for (let userItem in usersItems) {
var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
itemRef.once('value', snapshot => {
console.log("In once listener callback");
var item = snapshot.val()
newState.push({
id: itemRef.key,
title: item.title
});
})
}
console.log("After attaching once listeners");
this.setState({
items: newState
})
});
}
的调用已经很久了。
最简单的方法是在代码中添加一些日志语句:
setState()
此日志记录的输出将为:
在连接一次听众之前
附加一次听众后
一次听众回调
一次听众回调
...
这可能不是您预期的顺序。但它完全解释了为什么setState()
没有更新UI:数据尚未加载。
解决方案是在加载数据时调用componentWillMount() {
this.userRef.on('value', snapshot => {
this.setState({
displayName: snapshot.val().displayName
});
});
this.usersItemsRef.on('value', snapshot => {
let usersItems = snapshot.val();
let newState = [];
for (let userItem in usersItems) {
var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
itemRef.once('value', snapshot => {
var item = snapshot.val()
newState.push({
id: itemRef.key,
title: item.title
});
this.setState({
items: newState
})
})
}
});
}
。你可以将它移动到*回调:
setState()
这将为每个加载的项目调用Promise.all()
。通常,React非常适合处理此类增量更新。但是,如果它导致闪烁,您还可以使用componentWillMount() {
this.userRef.on('value', snapshot => {
this.setState({
displayName: snapshot.val().displayName
});
});
this.usersItemsRef.on('value', snapshot => {
let usersItems = snapshot.val();
let newState = [];
let promises = [];
for (let userItem in usersItems) {
var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
promises.push(itemRef.once('value'));
}
Promise.all(promises).then((snapshots) => {
snapshots.forEach((snapshot) => {
var item = snapshot.val()
newState.push({
id: itemRef.key,
title: item.title
});
});
this.setState({
items: newState
});
});
});
}
等待所有项目加载:
<svg class="shape" width="100%" height="100vh" preserveAspectRatio="none" viewBox="0 0 1440 800" xmlns:pathdata="http://www.codrops.com/">
<!-- background-image:
linear-gradient(-180deg, rgba(black,0.69) 0%, rgba(black,0.69) 100%),
linear-gradient(-180deg, rgba(black,0.100) 0%, rgba(black,0.100) 100%),
linear-gradient(-180deg, rgba(black,0.49) 0%, rgba(black,0.49) 100%);
background-blend-mode: luminosity, lighten, saturation; -->
<defs>
<filter id="f1" x="0" y="0" width="100%" height="100%">
<feFlood x="0" y="0" width="100%" height="100%" flood-color="black" flood-opacity="0.69" result="lumiFill"/>
<feBlend in="SourceGraphic" in2="lumiFill" mode="luminosity" result="lumiBlend"/>
<feFlood x="0" y="0" width="100%" height="100%" flood-color="black" flood-opacity="0.1" result="lightenFill"/>
<feBlend in="lumiBlend" in2="lightenFill" mode="lighten" result="lightenBlend"/>
<feFlood x="0" y="0" width="100%" height="100%" flood-color="black" flood-opacity="0.49" result="saturationFill"/>
<feBlend in="lightenBlend" in2="saturationFill" mode="saturation" result="saturationBlend"/>
</filter>
</defs>
<rect width="300" height="100" filter="url(#f1)" />
</svg>
答案 1 :(得分:0)
之前我没有使用过FirebaseDB,但我的猜测是它是异步的,并返回Promise
。
因此,在调用数据库以检索引用时使用.then
或await
。