skip to Main Content

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


  1. 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

    const ProductDetails = (props) => {
      const params = useParams();
      const id = params.productId;
      const ctx = useContext(ProductContext);
      const products = ctx.products;
      const product = products.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>
        </>
      );
    };
    

    You no longer need to pass the the product context on the route file

    Login or Signup to reply.
  2. 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 by id property found. When the app mounts and is rendering the "/products/:productId" route and ProductDetails component, the useEffect hook hasn’t run yet to fetch the data. The result is that product is undefined (because keep in mind that Array.prototype.find returns undefined 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:

    const ProductDetails = ({ items }) => {
      const { productId } = useParams();
      const product = items.find((pro) => String(pro.id) === productId);
    
      if (!product) {
        return <div>No product data.</div>;
      }
    
      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}
                  />
                </div>
                <div>
                  <p>{product.title}</p>
                  <p>${product.price}</p>
                  <p>{product.description}</p>
                  <button>Add To Cart</button>
                </div>
              </div>
            </div>
          </Card>
        </>
      );
    };
    

    Demo

    Edit my-useeffect-is-not-working-when-im-changing-the-route

    Suggestions

    You could further enhance the UI/UX by including that isLoading state in the ProductContextProvider 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 sets isLoading true, but then immediately sets isLoading back to false on the next line which runs before the fetch is made and the Promise chain settles.

    • Update the initial isLoading state to be true, for the cases when the component is initially mounted.
    • Return the fetched data from the fetchedData function.
    • Use the returned Promise from fetchedData to chain the thenables, using the .finally block to clear the loading state.
    const ProductContextProvider = ({ children }) => {
      const [products, setProducts] = useState([]);
      const [isLoading, setIsLoading] = useState(true);
    
      useEffect(() => {
        const fetchedData = async () => {
          setIsLoading(true);
          const response = await fetch("https://fakestoreapi.com/products/");
    
          if (!response.ok) {
            throw new Error("Something Went Wrong");
          }
          const data = await response.json();
          return data;
        };
    
        fetchedData()
          .then(setProducts)
          .catch((error) => {
            console.log({ error });
          })
          .finally(() => {
            setIsLoading(false);
          });
      }, []);
    
      return (
        <ProductContext.Provider value={{ isLoading, products }}>
          {children}
        </ProductContext.Provider>
      );
    };
    
    const AnimatedRoutes = () => {
      const location = useLocation();
    
      return (
        <AnimatePresence>
          <Routes location={location} key={location.pathname}>
            <Route path="/" element={<Home />} />
            <Route path="/products/:productId" element={<ProductDetails />} />
          </Routes>
        </AnimatePresence>
      );
    };
    
    const ProductDetails = ({ items }) => {
      const { productId } = useParams();
      const { isLoading, products } = useContext(ProductContext);
      const product = products.find((pro) => String(pro.id) === productId);
    
      if (isLoading) {
        return <div>Loading Data</div>;
      }
    
      if (!product) {
        return <div>No product data</div>;
      }
    
      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}
                  />
                </div>
                <div>
                  <p>{product.title}</p>
                  <p>${product.price}</p>
                  <p>{product.description}</p>
                  <button>Add To Cart</button>
                </div>
              </div>
            </div>
          </Card>
        </>
      );
    };
    

    Edit my-useeffect-is-not-working-when-im-changing-the-route (forked)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search