Reputation: 19
const cats = [
1, 2, 3
];
let arr = [];
const [images, setImages] = useState([]);
const generateCat = () => {
fetch(
"https://api.thecatapi.com/v1/images/search?size=small&limit=1&mime_types=jpg,png&width=200&height=200"
)
.then(response=>{return response.json()})
.then(data=>{
let finalResult = data[0]['url']
arr.push(finalResult)
console.log(arr)
return finalResult
})
};
for(let i=0;i<cats.length;i++){
generateCat()
setImages(arr)
console.log('Images: '+images)
}
My problem is that I'm encountering an issue with setState() causing an infinite loop in my React component. Specifically, I'm making an API call to retrieve an image of a cat and pushing the image URL to an array called arr. Then, I'm attempting to update the state of the component by calling setImages(arr) and logging the images variable to the console.
However, calling setImages() triggers a re-render of the component, which causes the for loop to execute again, leading to another API call, another push to arr, and another call to setImages(), resulting in an infinite loop.
I am expecting the state variable images
to have 3 img urls since the for loop is being iterated thrice.
Upvotes: 0
Views: 73
Reputation: 22797
You need to wrap your fetch operations in a useEffect
:
import { useState, useEffect } from "react";
const API_URL =
"https://api.thecatapi.com/v1/images/search?size=small&limit=1&mime_types=jpg,png&width=200&height=200";
async function getCat() {
const response = await fetch(API_URL);
const data = await response.json();
return data[0].url;
}
export default function App() {
const [pics, setPics] = useState([]);
useEffect(() => {
Promise.all(Array.from({ length: 3 }, getCat)).then(setPics);
}, []);
if (!pics.length) {
return "Loading...";
}
return (
<div className="app">
{pics.map((url) => (
<img key={url} src={url} alt="A kitten" />
))}
</div>
);
}
.app {
display: flex;
flex-direction: column;
}
<script type="text/babel">
const { useState, useEffect } = React;
const API_URL =
"https://api.thecatapi.com/v1/images/search?size=small&limit=1&mime_types=jpg,png&width=200&height=200";
async function getCat() {
const response = await fetch(API_URL);
const data = await response.json();
return data[0].url;
}
function App() {
const [pics, setPics] = useState([]);
useEffect(() => {
Promise.all(Array.from({ length: 3 }, getCat)).then(setPics);
}, []);
if (!pics.length) {
return "Loading...";
}
return (
<div className="app">
{pics.map((url) => (
<img key={url} src={url} alt="A kitten" />
))}
</div>
);
}
ReactDOM.createRoot(document.querySelector("#root")).render(<App />);
</script>
<div id="root"></div>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
Upvotes: 4
Reputation: 422
I am assuming this code is inside of a react component so here is modified example that should prevent the pre-rendering:
const cats = [
1, 2, 3
];
const ExampleComponent = () => {
const [images, setImages] = useState([]);
// this code will only be called on initial load of the component
useEffect(() => {
const generateCat = () => {
fetch(
"https://api.thecatapi.com/v1/images/search?size=small&limit=1&mime_types=jpg,png&width=200&height=200"
)
.then(response=>{return response.json()})
.then(data=>{
let finalResult = data[0]['url']
setImages(prev => [...prev, finalResult])
})
};
for(let i=0;i<cats.length;i++){
generateCat()
}
},[])
console.log('Images: '+images)
return <div>The Rest of your component here</div>
}
Upvotes: 1