I am trying to set a state generated by useState in an async function, but I found if I do like this, react would render my component infinitely.
This is a demo I made
export const App = () => {
const [nodes, setNodes] = useState([])
// some async refresh code, like http request, like axios.get("/list-nodes").then ...
const refresh = async () => {
let arr = []
for (let i = 0; i < 10; i++) {
arr.push(Math.random())
}
setNodes(arr)
}
refresh();
return (
<div>
{
nodes.map(v =>
<div>
value: {v}
</div>)
}
</div>
)
}
In the code, the rendering is continuously happening and the numbers are keeping changing.
May I ask how I am able to set a state in an async function correctly?
You need to use a useEffect hook to fetch data on the first render only. If not, the component fetches at every render, which happens every time the state is updated, which render the component ...
export const App = () => {
const [nodes, setNodes] = useState([]);
useEffect(()=>{
//Self calling async function
//Be carefull to add a ; at the end of the last line
(async () => {
let data = await fetch(url)
let json = await data.json()
setNodes(data)
})()
},[])
return (
<div>
{ nodes.map(node => <div>Value: {node}</div>) }
</div>
)
}
You can use useEffect
export default function App() {
const [nodes, setNodes] = useState([]);
const refresh = async () => {
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push(Math.random());
}
setNodes(arr);
};
useEffect(() => {refresh(); }, []);
return (
<div className="App">
{nodes.map((v) => (
<div>value: {v}</div>
))}
</div>
);}
Related
In my project I have the component ExportSearchResultCSV. Inside this component the nested component CSVLink exports a CSV File.
const ExportSearchResultCSV = ({ ...props }) => {
const { results, filters, parseResults, justify = 'justify-end', fileName = "schede_sicurezza" } = props;
const [newResults, setNewResults] = useState();
const [newFilters, setNewFilters] = useState();
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(true)
const [headers, setHeaders] = useState([])
const prepareResults = () => {
let newResults = [];
if (results.length > 1) {
results.map(item => {
newResults.push(parseResults(item));
}); return newResults;
}
}
const createData = () => {
let final = [];
newResults && newResults?.map((result, index) => {
let _item = {};
newFilters.forEach(filter => {
_item[filter.filter] = result[filter.filter];
});
final.push(_item);
});
return final;
}
console.log(createData())
const createHeaders = () => {
let headers = [];
newFilters && newFilters.forEach(item => {
headers.push({ label: item.header, key: item.filter })
});
return headers;
}
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters])
return (
<div className={`flex ${justify} h-10`} title={"Esporta come CSV"}>
{results.length > 0 &&
<CSVLink data={createData()}
headers={headers}
filename={fileName}
separator={";"}
onClick={async () => {
await setNewFilters(filters);
await setNewResults(prepareResults());
await setData(createData());
await setHeaders(createHeaders());
}}>
<RoundButton icon={<FaFileCsv size={23} />} onClick={() => { }} />
</CSVLink>}
</div >
)
}
export default ExportSearchResultCSV;
The problem I am facing is the CSV file which is empty. When I log createData() function the result is initially and empty object and then it gets filled with the data. The CSV is properly exported when I edit this component and the page is refreshed. I tried passing createData() instead of data to the onClick event but it didn't fix the problem. Why is createData() returning an empty object first? What am I missing?
You call console.log(createData()) in your functional component upon the very first render. And I assume, upon the very first render, newFilters is not containing anything yet, because you initialize it like so const [newFilters, setNewFilters] = useState();.
That is why your first result of createData() is an empty object(?). When you execute the onClick(), you also call await setNewFilters(filters); which fills newFilters and createData() can work with something.
You might be missunderstanding useEffect(). Passing something to React.useEffect() like you do
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters]) <-- look here
means that useEffect() is only called, when results or filters change. Thus, it gets no executed upon initial render.
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.
I am having an issue where my Listings component is running twice and I only want it to run once. I tried adding a counter below that would only run the code that grabs the data from the backend once but that did not work as you can see below it is still looping through the "grabListings" Function twice. I also tried a while loop and same result I am getting two results instead of one.
I believe my issue has to do with the way I am calling the Listings component on my LandingPage.
How can I run the grabListings component only once?
LandingPage
import NavBar from '../componets/NavBar/NavBar.js'
import Footer from '../componets/Footer/Footer'
import Slide1 from '../pictures/slide-1.jpg'
import Listings from '../componets/Listings'
import '../css/LandingPage.css';
const LandingPage = () => {
return (
<div className='wrapper'>
<NavBar />
<div className='top-img'>
<img src={Slide1} alt='E46 Slide Show Pic 1' />
</div>
<Listings />
<Footer />
</div>
)
}
export default LandingPage
Listings Component
const Listings = () => {
let cars = []
let links = []
let prices = []
let pictures = []
let counter = 0
const grabListings = async () => {
if (counter < 1) {
try {
// console.log("looped thorugh grab listing")
await axios.get('http://localhost:5000/scrape').then(async (res) => {
console.log("looped thorugh grab listing");
console.log(res);
const carsData = await (await axios.get('http://localhost:5000/car')).data;
cars.push(carsData);
const linksData = await (await axios.get('http://localhost:5000/link')).data;
links.push(linksData);
const pricesData = await (await axios.get('http://localhost:5000/price')).data;
prices.push(pricesData);
const picturesData = await (await axios.get('http://localhost:5000/picture')).data;
pictures.push(picturesData);
counter++
});
} catch (err) {
console.log(err);
counter++
};
};
console.log(cars);
console.log(links);
console.log(prices);
console.log(pictures);
};
grabListings();
return (
<>
<h1>{cars[0]}</h1>
</>
)
}
export default Listings
Result
Because React will re-render when state change, if you want stop re-render, put your grabListings() inside useEffect() like this:
useEffect(() => {
grabListings();
},[])
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
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]);
};