Is there a way to set state for each iteration of a foreach

Viewed 1553

I'm working with an API within a React Application and I'm trying to make the API calls come back as one promise.

I'm using the Promise.all() method which is working great.

I'm stuck trying to set the results of two API calls to state with their own name. The promise code is working correctly and I am trying to forEach() or map() over the two sets of data and save them to state with their own name.

I'm sure there is a simple solution but I've been scratching my head for far too long over this!

I've tried searching all the docs for .map and .forEach with no luck!

fetchData(){
this.setState({loading: true})
const urls = ['https://api.spacexdata.com/v3/launches/past', 'https://api.spacexdata.com/v3/launches']

let requests = urls.map(url => fetch(url));
Promise.all(requests)
  .then(responses => {
    return responses
  })
  .then(responses => Promise.all(responses.map(r => r.json())))
  .then(launches => launches.forEach(obj => {
    // I need to set both values to state here
  }))
  .then(() => this.setState({loading: false}))
  }

The API call returns two different arrays. I need to set both arrays to State individually with their own name. Is this possible?

3 Answers

If I understand your question correctly, a better approach might be to avoid iteration altogether (ie the use of forEach(), etc). Instead, consider an approach based on "destructuring syntax", seeing you have a known/fixed number of items in the array that is resolved from the prior promise.

You can make use of this syntax in the following way:

/* 
   The destructing syntax here assigns the first and second element of
   the input array to local variables 'responseFromFirstRequest'
   and 'responseFromSecondRequest' 
*/
.then(([responseFromFirstRequest, responseFromSecondRequest]) => {

      // Set different parts of state based on individual responses
      // Not suggesting you do this via two calls to setState() but
      // am doing so to explicitly illustrate the solution

      this.setState({ stateForFirstRequest : responseFromFirstRequest });
      this.setState({ stateForSecondRequest : responseFromSecondRequest });

      return responses
    })

So, integrated into your existing logic it would look like this:

fetchData() {
  this.setState({
    loading: true
  })
  
  const urls = ['https://api.spacexdata.com/v3/launches/past', 'https://api.spacexdata.com/v3/launches']

  const requests = urls.map(url => fetch(url));
  
  Promise.all(requests)
    .then(responses => Promise.all(responses.map(r => r.json())))
    .then(([responseFromFirstRequest, responseFromSecondRequest]) => {
    
      this.setState({ stateForFirstRequest : responseFromFirstRequest });
      this.setState({ stateForSecondRequest : responseFromSecondRequest });
    
      return responses
    })
    .then(() => this.setState({
      loading: false
    }))
}

If the two arrays won't interfere with each other in the state, is there a problem with just calling setState in each iteration?

.then(launches => launches.forEach(obj => {
  this.setState({ [obj.name]: obj });
}))

If you want to minimise the number of updates then you can create an Object from the two arrays and spread that into the state in one call:

.then(launches => this.setState({
    ...launches.reduce((obj, launch) => {
        obj[launch.name] = launch
        return obj
    }, {})
}))

forEach also provides the index as the second parameter. Wouldn't something like this work?

launches.forEach((obj, idx) => {
  if (idx === 0) {
    this.setState('first name', obj);
  } else if (idx === 1) {
    this.setState('second name', obj);
  }
})

Also, this portion literally does nothing..

  .then(responses => {
    return responses
  })

and the Promise.all() here also does nothing.

.then(responses => Promise.all(responses.map(r => r.json())))

should be

.then(responses => responses.map(r => r.json()))
Related