简体   繁体   中英

Update React state asynchronously from multiple sources

I'm trying to use immutability-helper to update my React state asynchronously from multiple sources (API calls). However, it seems to me that this is not the way to go, since state always gets updated with values from a single source only. Can someone explain me why is this the case and how to properly handle updates of my state?

import React from 'react';
import update from 'immutability-helper';

class App extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      updateInterval: 60,  // in seconds
      apiEndpoints: [
        '/stats',
        '/common/stats',
        '/personal/stats',
        '/general_info',
      ],
      items: [
        { itemName: 'One', apiUrl: 'url1', stats: {} },
        { itemName: 'Two', apiUrl: 'url2', stats: {} },
      ],
    };
    this.fetchData = this.fetchData.bind(this);
  }

  componentDidMount() {
    this.fetchData();
    setInterval(() => this.fetchData(), this.state.updateInterval * 1000);
  }

  fetchData() {
    this.state.apiEndpoints.forEach(endpoint => {
      this.state.items.forEach((item, i) => {
        fetch(`${item.apiUrl}${endpoint}`)
          .then(r => r.json())
          .then(res => {
            // response from each endpoint contains different keys.
            // assign all keys to stats object and set default 0 to
            // those which don't exist in response
            const stats = {
              statsFromFirstEndpoint: res.first_endpoint ? res.first_endpoint : 0,
              statsFromSecondEndpoint: res.second_endpoint ? res.second_endpoint : 0,
              statsFromThirdEndpoint: res.third_endpoint ? res.third_endpoint : 0,
            };
            this.setState(update(this.state, {
              items: { [i]: { $merge: { stats } } }
            }));
          })
          .catch(e => { /* log error */ });
      });
    });
  }

  render() {
    return (
      <div className="App">
        Hiya!
      </div>
    );
  }
}

export default App;

You should use the prevState argument in setState to make sure it always use the latest state:

this.setState(prevState =>
  update(prevState, {
    items: { [i]: { $merge: { stats } } },
  }));

Alternatively, map your requests to an array of promises then setState when all of them resolved:

const promises = this.state.apiEndpoints.map(endPoint =>
  Promise.all(this.state.items.map((item, i) =>
    fetch(), // add ur fetch code
  )));
Promise.all(promises).then(res => this.setState( /* update state */ ));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM