What it did is I fetched some products data in context using useEffect
and stored the fetched data in a state.
Context Component
import { useState, useEffect } from "react";
import ProductContext from "./product-context";
const ProductContextProvider = (props) => {
const [Products, setProducts] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchedData = async () => {
setIsLoading(true);
const response = await fetch("https://fakestoreapi.com/products/");
console.log("hi");
if (!response.ok) {
throw new Error("Something Went Wrong");
}
const data = await response.json();
setProducts(data);
};
fetchedData().catch((error) => {
setIsLoading(false);
});
setIsLoading(false);
}, []);
return (
<ProductContext.Provider
value={{ isLoading: isLoading, products: Products }}
>
{props.children}
</ProductContext.Provider>
);
};
export default ProductContextProvider;
Now I used this context in my dynamic route that I have created
Routing Component
import { Route, Routes, useLocation } from "react-router-dom";
import { AnimatePresence } from "framer-motion";
import ProductDetails from "../Pages/ProductDetails";
import Home from "../Pages/Home";
import { useContext } from "react";
import ProductContext from "../Store/product-context";
const AnimatedRoutes = () => {
const location = useLocation();
const ctx = useContext(ProductContext);
const products = ctx.products;
return (
<AnimatePresence>
<Routes location={location} key={location.pathname}>
<Route path="/" element={<Home />} />
<Route
path="/products/:productId"
element={<ProductDetails items={products} />}
/>
</Routes>
</AnimatePresence>
);
};
export default AnimatedRoutes;
And now attach below is <ProductDetails/>
component.
import React from "react";
import { useParams } from "react-router-dom";
import NavBar from "../Components/Navigaiton-Bar/NavBar";
import Card from "../UI/Card";
const ProductDetails = (props) => {
const params = useParams();
const id = params.productId;
const product = props.items.find((pro) => pro.id === +id);
return (
<>
<NavBar />
<Card>
<div className="flex items-center justify-center flex-wrap">
<div className="flex items-center w-full h-[100vh] ">
<div className="m-10">
<img
className="w-[800px] h-[300px]"
alt="gallary"
src={product.image}
></img>
</div>
<div>
<p>{product.title}</p>
<p>${product.price}</p>
<p>{product.description}</p>
<button>Add To Cart</button>
</div>
</div>
</div>
</Card>
</>
);
};
export default ProductDetails;
Now the problem here is when I’m visiting my dynamic page i.e. for example, "/products/1"
directly through url. My useEffect
is not fetching any data. And my Products state is an empty array.
2
Answers
I think you are using the context in a wrong way. Assuming the Provider is in the root parent you can already access the context directly inside
You no longer need to pass the the product context on the route file
I’ve copy/pasted your code into a running sandbox and the only way I can reproduce the issue you describe is if I manually edit the URL path in the address bar to navigate directly to any
"/products/:productId"
path.Issue
The issue is that the
ProductDetails
component doesn’t handle correctly when there is no matched product byid
property found. When the app mounts and is rendering the"/products/:productId"
route andProductDetails
component, theuseEffect
hook hasn’t run yet to fetch the data. The result is thatproduct
is undefined (because keep in mind thatArray.prototype.find
returnsundefined
when no matching array element is found) and the UI is attempting to access properties of a null/undefined value. This throws an exception.Solution
Update the
ProductDetails
component to handle missing, or not-found, data, which includes when it’s initially fetching it for the first time. Conditionally return alternative UI when there is no product data to display.Here’s a trivial example:
Demo
Suggestions
You could further enhance the UI/UX by including that
isLoading
state in theProductContextProvider
component and conditionally render a loading indicator/spinner/etc in the UI, and then conditionally render the above fallback or product UI once the data has been fetched.The
useEffect
hook logic is a little off.fetchedData
starts a Promise chain and setsisLoading
true, but then immediately setsisLoading
back to false on the next line which runs before the fetch is made and the Promise chain settles.isLoading
state to be true, for the cases when the component is initially mounted.data
from thefetchedData
function.fetchedData
to chain the thenables, using the.finally
block to clear the loading state.