I am making an API call to firebase from useEffect and the data returned is empty when the page loads for the first time but gets populated if I make any changes to the code and save it. I want the data to be present when the page loads for the first time.
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { motion } from "framer-motion";
import { getDatabase, ref, onValue } from "firebase/database";
import Helmet from "../components/Helmet/Helmet";
import "../styles/home.css";
import { Container, Row, Col } from "reactstrap";
import heroImg from "../assets/images/hero-img.png";
import Services from "../services/Services";
import ProductsList from "../components/UI/ProductsList";
import Clock from "../components/UI/Clock";
import counterImg from "../assets/images/counter-timer-img.png";
const Home = () => {
const [isLoading, setIsLoading] = useState(true);
const [products, setProducts] = useState([]);
const [trendingProducts, setTrendingProducts] = useState([]);
const [bestSalesProducts, setBestSalesProducts] = useState([]);
const [mobileProducts, setMobileProducts] = useState([]);
const [wirelessProducts, setWirelessProducts] = useState([]);
const [popularProducts, setPopularProducts] = useState([]);
const year = new Date().getFullYear();
useEffect(() => {
const fetchProducts = () => {
const db = getDatabase();
const thumbnailRef = ref(db, "Contents/");
onValue(thumbnailRef, (snapshot) => {
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((product) => {
return setProducts((oldArray) => [...oldArray, product]);
});
console.log(products);
}
});
};
const filteredTrendingProducts = products.filter(
(item) => item.category === "Kids"
);
const filteredBestSalesProducts = products.filter(
(item) => item.category === "Entertainment"
);
const filteredMobileProducts = products.filter(
(item) => item.category === "LifeStyle"
);
const filteredWirelessProducts = products.filter(
(item) => item.category === "wireless"
);
const filteredPopularProducts = products.filter(
(item) => item.category === "watch"
);
setTrendingProducts(filteredTrendingProducts);
setBestSalesProducts(filteredBestSalesProducts);
setMobileProducts(filteredMobileProducts);
setWirelessProducts(filteredWirelessProducts);
setPopularProducts(filteredPopularProducts);
setIsLoading(false);
fetchProducts();
}, []);
if (isLoading) {
return <div> Loading ... </div>;
}
return (
whileTap={{ scale: 1.2 }}
className="buy__btn store__btn"
>
<Link to="/shop">Visit Store</Link>
</motion.button>
</Col>
<Col lg="6" md="12" className="text-end counter__img">
<img src={counterImg} alt="" />
</Col>
</Row>
</Container>
</section>
<section className="new__arrivals">
<Container>
<Row>
<Col lg="12" className="text-center mb-5">
<h2 className="section__title">New Arrivals</h2>
</Col>
<ProductsList data={mobileProducts} />
<ProductsList data={wirelessProducts} />
</Row>
</Container>
</section>
<section className="popular_category">
<Container>
<Row>
<Col lg="12" className="text-center mb-5">
<h2 className="section__title">Popular in Category</h2>
</Col>
<ProductsList data={popularProducts} />
</Row>
</Container>
</section>
</Helmet>
);
};
export default Home;
I have tried adding products as the useEffect depencies but it creates an infinite loop fetching.
What do I have to do for the data to be fetched on the first page load?
CodePudding user response:
It is normal if console.log(products)
right after setProducts()
returns undefined
because setProducts()
is asynchronous and products
is not updated yet when logged.
Adding products
as a dependency will create a infinite loop because the same useEffect
is both setting and listening to products
.
However if you prefer, you could separate the logic for filters to a second useEffect
and add products
as its dependency, since the filtered are actually dependent on products
.
Example:
useEffect(() => {
const fetchProducts = () => {
const db = getDatabase();
const thumbnailRef = ref(db, "Contents/");
onValue(thumbnailRef, (snapshot) => {
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((product) => {
return setProducts((oldArray) => [...oldArray, product]);
});
console.log(products);
}
});
};
setIsLoading(false);
fetchProducts();
}, []);
useEffect(() => {
const filteredTrendingProducts = products.filter(
(item) => item.category === "Kids"
);
const filteredBestSalesProducts = products.filter(
(item) => item.category === "Entertainment"
);
const filteredMobileProducts = products.filter(
(item) => item.category === "LifeStyle"
);
const filteredWirelessProducts = products.filter(
(item) => item.category === "wireless"
);
const filteredPopularProducts = products.filter(
(item) => item.category === "watch"
);
setTrendingProducts(filteredTrendingProducts);
setBestSalesProducts(filteredBestSalesProducts);
setMobileProducts(filteredMobileProducts);
setWirelessProducts(filteredWirelessProducts);
setPopularProducts(filteredPopularProducts);
}, [products]);
CodePudding user response:
setState in react is async. Read more here. So when you set your state (products), it takes some time to actually update your state.
In order to fix your problem, you have 2 options.
First is to write another useEffect on your products state and move your logic there like this:
useEffect(() => {
const fetchProducts = () => {
const db = getDatabase();
const thumbnailRef = ref(db, "Contents/");
onValue(thumbnailRef, (snapshot) => {
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((product) => {
return setProducts((oldArray) => [...oldArray, product]);
});
console.log(products);
}
});
};
fetchProducts();
}, []);
useEffect(() => {
const filteredTrendingProducts = products.filter(
(item) => item.category === "Kids"
);
const filteredBestSalesProducts = products.filter(
(item) => item.category === "Entertainment"
);
const filteredMobileProducts = products.filter(
(item) => item.category === "LifeStyle"
);
const filteredWirelessProducts = products.filter(
(item) => item.category === "wireless"
);
const filteredPopularProducts = products.filter(
(item) => item.category === "watch"
);
setTrendingProducts(filteredTrendingProducts);
setBestSalesProducts(filteredBestSalesProducts);
setMobileProducts(filteredMobileProducts);
setWirelessProducts(filteredWirelessProducts);
setPopularProducts(filteredPopularProducts);
setIsLoading(false);
}, [procuts]);
And the other way is to use async/await and store your products in a temp variable and use the variable instead of the state:
useEffect(async () => {
let tempProducts = [];
const fetchProducts = async () => {
const db = await getDatabase();
const thumbnailRef = await ref(db, "Contents/");
await onValue(thumbnailRef, (snapshot) => {
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((product) => {
tempProducts = [...products, product]
return setProducts(tempProducts);
});
console.log(tempProducts);
}
});
};
const filteredTrendingProducts = tempProducts.filter(
(item) => item.category === "Kids"
);
const filteredBestSalesProducts = tempProducts.filter(
(item) => item.category === "Entertainment"
);
const filteredMobileProducts = tempProducts.filter(
(item) => item.category === "LifeStyle"
);
const filteredWirelessProducts = tempProducts.filter(
(item) => item.category === "wireless"
);
const filteredPopularProducts = tempProducts.filter(
(item) => item.category === "watch"
);
setTrendingProducts(filteredTrendingProducts);
setBestSalesProducts(filteredBestSalesProducts);
setMobileProducts(filteredMobileProducts);
setWirelessProducts(filteredWirelessProducts);
setPopularProducts(filteredPopularProducts);
setIsLoading(false);
await fetchProducts();
}, []);
You can also delete async/await if the functions are not async.