0

I am trying to add multiple objects to a state array using a forEach loop:

const App = () => {
const [pokemon, setPokemon] = useState([]);
axios.get('https://pokeapi.co/api/v2/pokemon') // Gives me 20 pokemon
.then(res => {
  res.data.results.map(p => p.url).forEach(url => { // Loops over endpoints of pokemon
    axios.get(url)
    .then(response => {
      setPokemon(response.data)
    })
  });
})
}

As you can probably guess, only the last item from the forEach loop is shown when I console.log pokemon as it's the last one to be set in the state.

The api I'm working with: https://pokeapi.co/.

I start with making a call to https://pokeapi.co/api/v2/pokemon which gives me 20 pokemon, each pokemon object comes with an endpoint to get more information about it and it's the information from these endpoints I want to store as objects in the state.

Thanks for any help, if you know of a better way I can do this feel free to let me know.

0

4 Answers 4

1

I suggest you use Promise.all() inside of a useEffect() hook. By using useEffect, you can run your fetch code once when the component mounts (not each time it renders). Using Promise.all() allows you to pass an array of promises (which is what axios.get() returns), and resolve each Promise to one. This one Promise can resolve to an array of responses that each axios.get() call resulted in. This allows you to only set your state once.

See working example:

const {useState, useEffect} = React;
const App = () => {
  const [pokemon, setPokemon] = useState([]);
  
  useEffect(() => {
    axios.get('https://pokeapi.co/api/v2/pokemon')
      .then(res => Promise.all(res.data.results.map(pokemon => axios.get(pokemon.url))))
      .then(arr => setPokemon(arr.map(response => response.data)));
  }, []);
  
  return (<div>
    {pokemon.map(({id, species}) => <p key={id}>{species.name}</p>)}
  </div>);
}

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js" integrity="sha512-bZS47S7sPOxkjU/4Bt0zrhEtWx0y0CRkhEp8IckzK+ltifIIE9EMIMTuT/mEzoIMewUINruDBIR/jJnbguonqQ==" crossorigin="anonymous"></script>

Sign up to request clarification or add additional context in comments.

3 Comments

I have these functions that I need to fire probably inside the first .then but I can't seem to do it without it breaking: setLoading(false) setNextPageUrl(res.data.next) setPrevPageUrl(res.data.previous).
@A.Copland you can create a body {}, and then return the result of Promise.all, youcan put your function calls before the return: .then(res => {setLoading(false); ... return Promise.all(res.data.results.map(pokemon => axios.get(pokemon.url)))})
Thanks @Nick Parsons that worked a treat.
1

Just create a temporary array, add all the data which you get from the API to that, and later set the state using that variable. You don't have to set the state every time - you can set it at once.

const App = () => {
  const [pokemon, setPokemon] = useState([]);
  const tempArr = [];
  axios.get('https://pokeapi.co/api/v2/pokemon') // Gives me 20 pokemon
  .then(res => {
    res.data.results.map(p => p.url).forEach(url => { // Loops over endpoints of pokemon
      axios.get(url).then(response => {
        tempArr.push(response.data);
      })
    });
  });
  setPokemon(tempArr);
}

5 Comments

This will call setPokemon(tempArr); before anything is pushed to tempArr
So this looks to have worked, I logged it to the console and I can see 4 different array's, 3 have nothing in but the final one has them all in. Thanks for the help
@NickParsons Yes, you are correct. I am fairly new to JS and I tend to think of the code execution in sync format. Would it be helpful to use axios.all() in place of inner axios and the set state in .then of axios.all call just so that we set the state only once?
@ShubhamJawandhiya yeah, that's exactly how I would do it (I actually just added an answer doing it this way). However, I've used Promise.all instead of axios.all, as axios.all is deprecated.
Ok. I didn't know about the deprecation. Thanks for the info. @A.Copland please refer to Nick's answer for the correct way to do it.
0

Try setPokemon([...pokemon , response.data] )

Comments

0

You should probably use

setPokemon (pokemon.concat(response.data)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.