How to avoid unnecessary API calls with useEffect? - javascript

I'm still beginner to ReactJS and I'm having trouble rendering a list.
I don't know why, all the time calls are being made to my API. Since I don't put any dependency on useEffect, that is, I should only render my function once.
I don't understand why this is happening. Can you tell me what I'm doing wrong?
Here's my code I put into codesandbox.io
import React from "react";
import axios from "axios";
import "./styles.css";
const App = () => {
const BASE_URL = "https://pokeapi.co/api/v2";
const [pokemons, setPokemons] = React.useState([]);
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
data.results.map((pokemon) => getPokeType(pokemon));
};
const getPokeType = async (pokemon) => {
const { data } = await axios.get(pokemon.url);
setPokemons((prev) => [...prev, data]);
};
React.useEffect(() => {
getAllPokemons();
}, []);
console.log(pokemons);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{pokemons.map((pokemon) => (
<p key={pokemon.id} style={{ color: "blue" }}>
{pokemon.name}
</p>
))}
</div>
);
};
export default App;
Thank you very much in advance.

Your issue is that you are calling setPokemons inside getPokeType (which is called for each data in part). Your useEffect is called just once (as expected) and the ${BASE_URL}/pokemon call is executed just once too. But getPokeType is called 20 times and the pokemons state is changed 20 times as well (once for each instance from data.results).
What I would recommend in your case (instead of what you have now) is:
Create a list of all the pokemons and
Set the state just once at the end.
So something like:
...
const getPokeType = async (pokemon) => {
const { data } = await axios.get(pokemon.url);
return data;
};
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
const pokemons = await Promise.all(
data.results.map((pokemon) => getPokeType(pokemon))
);
setPokemons(pokemons);
};
React.useEffect(() => {
getAllPokemons();
}, []);
...

I was just having the same issue in my project the way I solved is by moving the function definition inside the useEffect
React.useEffect(() => {
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
data.results.map((pokemon) => getPokeType(pokemon));
};
getAllPokemons();
}, []);
If this solves your problem please accept the answer.

Related

React useState array empty on initial load but after editing code while app is running array fills?

This is going to be really hard to explain, but here goes. I am building a React card grid with a filter. The data is pulled from an MySQL AWS API I built. The .tags property is JSON with an array that stores each tag associated with the card. I have written Javascript in App.jsx to turn this JSON into an object, and then store every unique tag in a piece of state. See code below:
//App.jsx
import { useEffect, useState } from 'react';
import '../assets/css/App.css';
import Card from './Card';
import Filter from './Filter'
import {motion, AnimatePresence} from 'framer-motion'
function App() {
const [cards, setCards] = useState([]);
const [filter, setFilter] = useState([]);
const [activeFilter, setActiveFilter] = useState("all");
const [tags,setTags] = useState([]);
useEffect(() => {
fetchData();
}, []);
/*useEffect(() => {
console.log(tags);
console.log(activeFilter);
}, [activeFilter,tags]);
*/
const getTags = () => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
const fetchData = async () => {
const data = await fetch("<<api>>");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags();
}
return (
<div className="App">
<Filter
cards={cards}
setFilter={setFilter}
activeFilter={activeFilter}
setActiveFilter={setActiveFilter}
/>
<motion.div layout className="Cards">
<AnimatePresence>
{filter.map((card) => {
return <Card key={card.id} card={card}/>;
})}
</AnimatePresence>
</motion.div>
</div>
);
}
export default App;
The problem that I am having is that when I run the app initially, the tags state is empty when inspecting from React Dev tools. However, when I keep the app running, and then add something like a console.log(tags); before setTags(tags) is called in the getTags() function, the data suddenly appears in the state. If someone could explain why the state seems to be empty even though I am updating it on the initial render that would be really appreciated.
You are running getTags on empty array. setCards doesn't set the const variable instantly. New values will be present in the next render cycle.
Try adding cards param
const getTags = (cards) => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
And use it like this:
const fetchData = async () => {
const data = await fetch("API url");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags(cards);
}

React-How to display make objects loop in react

Here is what i tried
episode.js
import Parser from "rss-parser";
import React from "react";
export default function Episode() {
const parser = new Parser();
const url1 = "https://anchor.fm/s/75abc654/podcast/rss";
const [data, setData] = React.useState({});
(async () => {
let data = await parser.parseURL(url1);
setData(data);
// console.log(data.title)
// data.items.forEach((item) => {
// console.log(item.title)
//console.log(item.pubDate.slice(5, 17))
//console.log(item.enclosure.url)
// console.log(item.itunes.image)
});
})();
return(
<h1>{item.title}</h1>
{data.items.map((item, index)=>{
return(
<h1>{item.title}</h1>
)})}
)
}
And the output is blank screen.. No error in console.. Help me to get the data from the rss feed without blank screen
You're calling async method unlimited time! You need to call it once via useEffect just when component rendered for first time:
import Parser from "rss-parser";
import React, { useEffect } from "react";
function Episode() {
const parser = new Parser();
const url1 = "https://anchor.fm/s/75abc654/podcast/rss";
const [data, setData] = React.useState({});
useEffect(() => {
(async () => {
let data = await parser.parseURL(url1);
console.log(data);
setData(data);
})();
}, []);
return (
<>
{data.items?.map((item, index) => (
<h1>{item.title}</h1>
))}
</>
);
}
export default function App() {
return <Episode />;
}
Just replace your async function with a useEffect hook like this
useEffect(() => {
async function fetchMyAPI() {
let data = await parser.parseURL(url1);
setData(data);
// console.log(data.title)
// data.items.forEach((item) => {
// console.log(item.title)
//console.log(item.pubDate.slice(5, 17))
//console.log(item.enclosure.url)
// console.log(item.itunes.image)
}
fetchMyAPI();
}, []);
This will be executed once every time when your component is loaded on screen
Also change the data.item.map to
{data.items?.map((item, index) => {
return <h1>{item.title}</h1>;
})}
Else it will throw error on first render

logging the data but not rendering p tag , why?

I am using firebase firestore and i fetched the data , everything is working fine but when i am passing it to some component only one item gets passed but log shows all the elements correctly.
I have just started learning react , any help is appreciated.
import React, { useEffect, useState } from 'react'
import { auth, provider, db } from './firebase';
import DataCard from './DataCard'
function Explore() {
const [equipmentList, setEquipments] = useState([]);
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
data.docs.forEach(item => {
setEquipments([...equipmentList, item.data()]);
})
}
useEffect(() => {
fetchData();
}, [])
equipmentList.forEach(item => {
//console.log(item.description);
})
const dataJSX =
<>
{
equipmentList.map(eq => (
<div key={eq.uid}>
{console.log(eq.equipment)}
<p>{eq.equipment}</p>
</div>
))
}
</>
return (
<>
{dataJSX}
</>
)
}
export default Explore
You have problems with setting fetched data into the state.
You need to call setEquipments once when data is prepared because you always erase it with an initial array plus an item from forEach.
The right code for setting equipment is
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
setEquipments(data.docs.map(item => item.data()))
}

React API Fetch resolve promise

I have a functional component Users.js and a seperate Api.js.
Below the code which gives me the error:
// Function Users.js
useEffect(() => {
console.log(fetchUsers());
const data = fetchUsers();
setUsers(data);
//setUsers(fetchUsers());
}, []);
// Api.js
export const fetchUsers = async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/users');
return await data.json();
};
I am getting the following error:
TypeError: users.map is not a function
This is because I return a promise, which I could see by evaluating the console.log(fetchUsers()).
How can I fix this issue, because it worked as long as I used the function fetchUsers inside my function and not in a seperate file...
I tried to update your code to give you two examples:
export default function App() {
const [users, setUsers] = useState();
useEffect(() => {
async function getData() {
const data = await fetchUsers();
setUsers(data);
}
getData()
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox </h1>
{
(users || []).map(item => <span>{item.name}</span>)
}
</div>
);
}
export const fetchUsers = async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/users');
return await data.json();
};
The second one is better than the first one, I used .then() on your function:
export default function App() {
const [users, setUsers] = useState();
useEffect(() => {
fetchUsers().then(items => setUsers(items))
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox </h1>
{
(users || []).map(item => <span>{item.name}</span>)
}
</div>
);
}
In both cases I am able to retrieve the users.
I tried it in a single file but you can of course move the functions where you prefer.
I suggest you, before to render data, to check if the users array exists (users || []) or to give it a default empty array value [users, setUsers] = useState([])
You can handle the promise which is returned and then set the state.
fetchUsers().then((result) => setUsers(result));

How to use useState with React-redux-firebase to re-render

I'm exploring hooks with react-redux-firebase but my "setDataProducts" is behaving oddly.
I'm using useEffect() like I could use componentDidMount() but not sure if this is the right way.
export default function ProductList() {
const [dataProducts, setDataProducts] = useState([]);
const firestore = useFirestore();
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
data.docs.forEach((product) => {
setDataProducts([...dataProducts, product.data()]);
console.log(product.data());
});
};
useEffect(() => {
fetchProducts();
}, []);
return (
<div>
{isLoaded &&
dataProducts.map((product) => {
return (
<div>
<h4>{product.title}</h4>
<h3>{product.price}</h3>
</div>
);
})}
</div>
);
}
I cannot render the both products I have in Firestore. Only One is rendering... So I dont understand. Should not it rerender when state is updated ?
Thanks for reply
We can see there was not rerendering
I think it is because you called setDataProducts again before dataProducts updated.
Please replace fetchProducts method with my code following:
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
const newProducts = data.docs.map((product) => product.data());
console.log(newProducts);
setDataProducts([...dataProducts, ...newProducts]);
};

Categories

Resources