I Am creating a shoe website and but there is a problem in the following component it normally works fine but when i try to copy the url paste in another tab and try to open it directly then it gives me the error because i am supposed to recieve data from parent route and i tried the following work around but its not working but why
export default function Article() {
console.log("component is rendered");
const { user } = useAuth();
const navigate = useNavigate();
const {id} = useParams();
// article data coming from previous route
const location = useLocation()
const { article } = location.state || {};
// cart
const { addToCart , articleLoadingState , setArticleLoadingState } = useCart();
const [sales, setSales] = useState(0);
const [Article,setArticle] = useState(article);
console.log(article)
useEffect(() => {
const fetchArticleData = async () => {
console.log("fetch article data");
if (!article) {
try {
const docRef = doc(db, "Articles", id);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log(docSnap.data());
setArticle(docSnap.data());
}
} catch (err) {}
} else {
setArticle(article);
}
};
fetchArticleData(); // Call the async function
}, [article,id]);
console.log(article) prints undefined in the console but then why is console.log("fetch article data") not being printed in console that means the hook is not being executed but why and console.log("component is rendered"); is also working and the error i recieve is
Article.jsx:49 Uncaught TypeError: Cannot read properties of undefined (reading 'colors')
at Article (Article.jsx:49:76)
because of this line in code
const [selectedColor, setSelectedColor] = useState(Object.keys(Article.colors)[0],);
this is the code of app component
function App() {
return (
<>
<header>
<Header />
</header>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products/:category" element={<Products />} />
<Route path="/products/:category/:id" element={<Article />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/sizeguide" element={<SizeGuide />} />
<Route path="/return-exchange-policy" element={<Return_Exchange_Policy />} />
<Route path="/terms-of-service" element={<Terms_of_service />} />
{/* Protected Routes */}
<Route path="/cart" element={ <ProtectedRoute> <Cart /> </ProtectedRoute> }/>
<Route path="/wishlist" element={ <ProtectedRoute> <WishList /> </ProtectedRoute> }/>
<Route path="/orders" element={ <ProtectedRoute> <Orders /> </ProtectedRoute> }/>
<Route path="/checkout" element={ <ProtectedRoute> <CheckOut /> </ProtectedRoute>} />
</Routes>
</>
);
}
export default App;
full code of component
import React, { useState, useEffect } from "react";
import { MdAddShoppingCart } from "react-icons/md";
import { Button , Spinner } from "@nextui-org/react";
import { fireIcon } from "../../assets/images";
import { useNavigate , useLocation , useParams} from "react-router-dom";
import { useCart } from "../../context/CartContext";
import { useAuth } from "../../context/AuthManager";
import { doc, getDoc } from "firebase/firestore";
export default function Article() {
console.log("component is rendered");
const { user } = useAuth();
const navigate = useNavigate();
const {id} = useParams();
// article data coming from previous route
const location = useLocation()
const { article } = location.state || {};
// cart
const { addToCart , articleLoadingState , setArticleLoadingState } = useCart();
const [sales, setSales] = useState(0);
const [Article,setArticle] = useState(article);
console.log(article)
useEffect(() => {
const fetchArticleData = async () => {
console.log("fetch article data");
if (!article) {
try {
console.log("fetch article data");
const docRef = doc(db, "Articles", id);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log(docSnap.data());
setArticle(docSnap.data());
}
} catch (err) {}
} else {
setArticle(article);
}
};
fetchArticleData(); // Call the async function
}, [article,id]);
// article data
const [selectedColor, setSelectedColor] = useState(Object.keys(Article.colors)[0],);// currently selected color
const [selectedImage, setSelectedImage] = useState(0); // Track the active image index
const [imageCounter, setImageCounter] = useState(1); // Track the current image number (1-indexed)
const [isTransitioning, setIsTransitioning] = useState(true); // To control the sliding effect
const [selectedSize, setSelectedSize] = useState(Article.sizes[0]); // Default to the first size
const [amount, setAmount] = useState(1);
// destructing the article data
const { colors, images } = Article;
const articleData = {
articleNo: Article.id,
originalPrice: Article.price,
discount: Article.discount,
available: Article.available,
images,
category: Article.collection,
vendor: Article.vendor,
sizes: Article.sizes,
colors,
};
// fn for adding component to cart
const handleAddToCart = async () => {
setArticleLoadingState(true);
const cartItem = {
id: Article.id,
originalPrice: Article.price,
discount: Article.discount,
available: Article.available,
images: images[selectedColor],
category: Article.collection,
vendor: Article.vendor,
size: selectedSize,
color: selectedColor,
quantity: amount,
};
try {
await addToCart(cartItem); // Add item to the cart
} catch (error) {
} finally {
setArticleLoadingState(false);
}
};
const discountedPrice =
articleData.originalPrice * (1 - articleData.discount / 100);
const handleColorChange = (color) => {
setSelectedColor(color);
setSelectedImage(0); // Reset selected image when changing color
setImageCounter(1); // Reset counter when changing color
setIsTransitioning(false); // Temporarily stop the sliding effect
};
const handleImageSelect = (index) => {
setSelectedImage(index); // Update the active image
setImageCounter(index + 1); // Update the counter (1-indexed)
};
// Set up the automatic image change every 5 seconds
useEffect(() => {
const interval = setInterval(() => {
setSelectedImage((prevIndex) => {
let nextIndex = prevIndex + 1;
// If the next index exceeds the number of images, reset to 0
if (nextIndex >= articleData.images[selectedColor].length) {
nextIndex = 0;
setIsTransitioning(false); // Stop the sliding effect temporarily
setTimeout(() => {
setIsTransitioning(true); // Re-enable the sliding effect after a brief pause
}, 0); // Wait for a short period before re-enabling the transition
}
setImageCounter(nextIndex + 1); // Update counter (1-indexed)
return nextIndex;
});
}, 5000); // Change every 5 seconds
return () => clearInterval(interval); // Clean up the interval when the component unmounts
}, [selectedColor, articleData.images]);
useEffect(() => {
// Set initial sales value to a random number between 10 and 15
const initialSales = Math.floor(Math.random() * (15 - 10 + 1)) + 10;
setSales(initialSales);
// Set up interval to increment sales by 1, 2, or 3 every 2 minutes
const interval = setInterval(
() => {
const increment = Math.floor(Math.random() * 3) + 1; // Random value between 1 and 3
setSales((prevSales) => prevSales + increment);
},
5 * 60 * 1000,
); // 2 minutes in milliseconds
return () => clearInterval(interval); // Clean up the interval on component unmount
}, []);
return (
<>
<section className="md:w-full w-[98%] mt-10 max-w-7xl mx-auto flex flex-col md:flex-row md:flex-wrap gap-x-2 justify-center md:items-start items-center gap-y-4">
<div className="flex lg:flex-row-reverse flex-col items-start md:w-2/4 w-[95%] md:mr-6 justify-center gap-y-2 gap-x-2">
<div className="w-full min-w-[250px] relative overflow-hidden bg-hero">
{/* Main Image with Sliding Effect */}
<div
className={`flex transition-transform duration-1000 ease-in-out ${isTransitioning ? "" : "transform-none"
}`}
style={{
transform: isTransitioning
? `translateX(-${selectedImage * 100}%)` // Slide images horizontally
: "none", // Disable the slide effect temporarily
}}
>
{articleData.images[selectedColor].map(
(image, index) => (
<img
key={index}
src={image}
alt={`Main Image ${index + 1}`}
className="w-full"
/>
),
)}
</div>
</div>
<div className="flex lg:flex-col flex-row gap-x-2 gap-y-2 lg:mt-4 sm:mt-1 mt-0">
{articleData.images[selectedColor].map(
(image, index) => (
<div
key={index}
className={`cursor-pointer rounded-md p-1 w-16 h-20 sm:w-14 sm:h-16 flex items-center ${selectedImage === index ? "border-1 border-black" : ""}`}
onClick={() => handleImageSelect(index)} // Update the active image on click
>
<img
src={image}
alt={`Small Image ${index + 1}`}
className="w-11/12"
/>
</div>
),
)}
</div>
</div>
<div className="w-[95%] md:w-2/5 min-w-[250px] flex flex-col gap-y-3 order-3 md:order-none md:mt-0 mt-6 pl-6 md:pl-6">
<p className="text-gray-600 font-asul">FOOTPULSE</p>
<h1 className="md:text-2xl text-xl text-black font-bold flex w-full justify-between">
{articleData.articleNo}
{articleData.discount > 0 && (
<div className="rounded-full bg-red-500 w-fit text-white px-4 py-1 text-sm font-bold mr-16">
-{articleData.discount}%
</div>
)}
</h1>
<div className="flex text-[#DD3327] ml-6 my-3 text-base">
<img src={fireIcon} alt="" className="mr-2" />
<span>{sales} sold in last 12 hours</span>
</div>
<div className="flex gap-x-4 items-center">
<p className="text-red-500 font-bold text-2xl flex">
Rs.{discountedPrice.toFixed(2)}
</p>
{articleData.discount > 0 && (
<p className="text-gray-500 text-2xl font-bold line-through text-md mt-0">
Rs.{articleData.originalPrice}
</p>
)}
</div>
<hr className="border-gray-300 md:my-1 my-3" />
<div className="flex flex-col text-gray-700 font-palanquin text-lg gap-y-2">
<p className="text-black">
<span className="text-gray-600"> Available </span>:{" "}
{articleData.available
? "In Stock"
: "Out of Stock"}
</p>
<p className="text-black">
<span className="text-gray-600 "> Vendor </span>:{" "}
{articleData.vendor}
</p>
<p className="text-black">
<span className="text-gray-600"> Type </span>:{" "}
{articleData.category}
</p>
</div>
<div className="flex flex-wrap gap-2 items-center mt-4">
<span className="font-bold">Sizes:</span>
{articleData.sizes.map((size, index) => (
<span
key={index}
className={`px-3 py-1 border rounded-full cursor-pointer hover:bg-gray-200 ${selectedSize === size ? "border-black bg-gray-300" : ""
}`}
onClick={() => setSelectedSize(size)} // Update the selected size
>
{size}
</span>
))}
</div>
<div className="flex flex-wrap gap-2 items-center mt-4">
<span className="font-bold">Colors:</span>
{Object.keys(articleData.colors).map((color, index) => (
<span
key={index}
className={`w-6 h-6 rounded-full border border-gray-600 cursor-pointer transition-all duration-200 ${selectedColor === color ? "border-2 border-gray-600" : ""}`}
style={{
backgroundColor: articleData.colors[color],
}}
onClick={() => handleColorChange(color)}
></span>
))}
</div>
<div className="flex w-[95%] mx-auto flex-col sm:flex-row gap-4 mt-4">
<div className="flex items-center justify-between gap-2 border border-gray-300 rounded-full px-3 py-2 text-black w-[30%]">
<button
className="text-md font-bold"
onClick={() =>
setAmount((prev) => Math.max(prev - 1, 1))
}
>
-
</button>
<p className="text-md font-bold">{amount}</p>
<button
className="text-md font-bold"
onClick={() => setAmount((prev) => prev + 1)}
>
+
</button>
</div>
<Button
className="bg-gray-950 text-white py-2 font-semibold"
radius="full"
onClick={handleAddToCart}
disabled={articleLoadingState}
>
{articleLoadingState ? (
<Spinner size="sm" />
) : (
<>
Add to Cart
<MdAddShoppingCart
size={20}
/>
</>
)}
</Button>
<Button
radius="full"
color="success"
className="text-white"
onClick={() => {
handleAddToCart()
if (user) {
addToCart();
navigate("/checkout")
} else {
navigate("/login")
}
}}
>
Buy Now
</Button>
</div>
</div>
</section>
</>
);
}
and the url looks like this
http://localhost:5173/products/casual/AP34524
tell me if you need anything else