在SetState之后React不更新渲染

时间:2018-02-07 03:21:21

标签: javascript reactjs firebase firebase-realtime-database

我已经玩了一段时间了。我正在从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>
      );
  }
}

2 个答案:

答案 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。 因此,在调用数据库以检索引用时使用.thenawait