Problem:
In my App function (reactjs here), I am making a call to an api, and then in the .then I am making many more api calls with await to get more details. This is an illustration using a public api for pokemon. The main call loads a summary, then each record is called again to fill in the details.
The problem I am having is that App() is called many, many times, and so the api calls are repeated over and over eventually leading to the browser to crash. I think the reason why is that I am updating the progress state as I move through the loop which causes the function to rerun. But I’m not sure how to stop it calling the initial axios.get again.
import './App.css';
import axios from 'axios';
import React, {useState} from 'react'
import "./styles.css"
function App() {
let [allDataJson, setAllData] = useState(null);
let [progress, setProgress] = useState(0);
axios.get("https://pokeapi.co/api/v2/pokemon/?offset=0&limit=200").then(async (response) => {
const newData = [];
// API returns result in blocks, with next pointing to next block
do{
for(var item of response.data.results)
newData.push(item);
response = await axios.get(response.data.next);
} while(response.data.next);
for(const item of newData) {
item.detail = await getDetails(item.url);
setProgress(progress+1);
}
setAllData(JSON.stringify(newData));
});
return (
<div>
{ allDataJson ? (
<div>Completed Load</div>
) : (
<div>Loading, please wait....{progress}</div>
)
}
</div>
);
}
const cache = {};
export async function getDetails(url) {
// url is in form "host/.../id/" where id is unique number.
const split = url.split('/');
const code = 'code' + split[split.length - 2];
if(cache.hasOwnProperty(code))
return cache[code];
else {
const response = await axios.get(url);
const data = response.data;
cache[code] = data;
return data;
}
}
export default App;
I tried this ugly hack. This did prevent it from calling the api too many times but the html did not render.
function App() {
let [allDataJson, setAllData] = useState(null);
let [progress, setProgress] = useState(0);
if(!started) {
started = true;
axios.get("https://pokeapi.co/api/v2/pokemon/?offset=0&limit=200").then(async (response) => {
... same as above ...
});
}
return (
<div>
{ allDataJson ? (
<div>Completed Load</div>
What am I doing wrong? Is there a better way to achieve this goal. (It takes a while to load all this, so really I’d like a simple progress bar, or maybe even a background “thread” as in a setTimeout to do the same thing.)
Obviously, fairly new to react
Solution:
You should wrap the axios call in a useEffect
function.
Import useEffect
alongside useState
at the top:
import React, {useState, useEffect} from 'react'
And you App function should look more like this:
function App() {
let [allDataJson, setAllData] = useState(null);
let [progress, setProgress] = useState(0);
useEffect(() => {
axios.get...
}, []) // notice the empty array will cause the function to only be called once when the app loads since it is not dependent on any other variables.
return (
...
);
}