【问题标题】:React Not Updating Render After SetState在 SetState 之后反应不更新渲染
【发布时间】:2018-07-17 06:01:14
【问题描述】:

我已经玩了一段时间的代码了。我正在从 Firebase 获取数据并将该数据中的对象列表填充到此 UserProfile 页面上。我只是希望它能够正常工作,以便当我进入个人资料页面时,那里会列出他们的项目列表。

问题在于,使用此版本的代码,我必须单击配置文件链接两次才能显示项目,但显示名称显示正常。我知道 setState 是异步的。我尝试在 setState 的回调中设置状态。我已经尝试过检查快照是否事先存在。我已经尝试过 componentDidMount 和 componentDidUpdate。这些东西都没有帮助,我只是最终无法调用 this.state.items.map 或者 newState 是一些空的,奇怪的东西。我不知道我现在哪里错了。

当我对此进行控制台调试时,看起来 setState 正在被调用,而到目前为止还没有从 Firebase 获取任何内容,并且以后再也不会被调用。

也许我设置 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>
      );
  }
}

【问题讨论】:

    标签: javascript reactjs firebase firebase-realtime-database


    【解决方案1】:

    数据是从 Firebase 异步加载的,因为它可能需要不确定的时间。当数据可用时,Firebase 会调用您传入的回调函数。但到那时您对 setState() 的调用早已结束。

    查看这一点的最简单方法是在代码中添加一些日志语句:

    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() 没有更新 UI:数据尚未加载。

    解决方案是在数据加载完毕后调用setState()。您可以通过将其移动到**回调中来做到这一点:

    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()。通常 React 可以很好地处理这种增量更新。但万一它导致闪烁,你也可以使用Promise.all()等待所有项目加载完成:

    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
          });
        });
      });
    }
    

    【讨论】:

    • 像魅力一样工作!是的,当我在控制台调试器中运行它时,我当然没想到这个命令,但现在它是有道理的。这两种方法都有效,我将使用 Promise 方法,只是将行 snapshot.forEach((snapshot) 更改为 snapshots.forEach((snapshot)。非常感谢您的详细解答!
    【解决方案2】:

    我以前没有使用过 FirebaseDB,但我猜它是异步的,并返回一个 Promise。 因此在调用数据库检索引用时使用.thenawait

    【讨论】:

      猜你喜欢
      • 2020-05-06
      • 1970-01-01
      • 2019-12-13
      • 2016-10-20
      • 1970-01-01
      • 2017-11-01
      • 1970-01-01
      • 2021-12-06
      • 1970-01-01
      相关资源
      最近更新 更多