I have category pages for Drinks and Foods that use the same component, CategoryPage
. Their URLs are /category/food
and /category/drinks
; when I'm moving from Drinks to route other than Foods or vice versa, it keeps the same state value as it was on the previous category route, but I need it to reset as it was while assigning its value.
The main idea is to show a spinner while item pictures are still loading; if this is not the correct way to do so, I would be glad if you showed me the correct way to solve this problem.
General Category page code:
const CategoryPage = () =>{
const { name } = useParams()
const loading = useSelector(state => state.shop.loading)
const categoryItems = useSelector(state => state.shop.categoryItems)
const dispatch = useDispatch()
//counting loaded images that should be shown in category page
const [loadedImages,setLoadedImages] = useState(0)
useEffect(() => {
dispatch(getShopData(`categories/${name}/products`,'category'))
},[dispatch,name])
useEffect(() =>{
//when url params change, it means the user moved to the new category page
// so loadedImagesState must be reset to 0 with useffect cause without it, the state will remain the same
// as it was at the end of previous category page
setLoadedImages(0)
},[name])
return(
<>
<img src={bg} className={classes["bg"]}/>
{categoryItems.length > 0 &&
<div className={classes["container"]}>
{categoryItems.map((product,id) =>
<CategoryItem key={id} product={product} setLoadedImages={setLoadedImages} />
)}
</div>
}
{(loading || loadedImages != categoryItems.length) &&
<div className="page-load-wrapper">
<Loader manualStyles={{width:"75px",height:"75px"}} />
</div>
}
</>
)
}
CategoryItem code:
const CategoryItem = ({ product, stylesFor="", setLoadedImages}) =>{
const dispatch = useDispatch()
const addItem = () =>{
dispatch(cartActions.addItem(product))
}
const notifyImageLoad = () =>{
if(setLoadedImages){
setLoadedImages(prev => prev +1)
}
}
return(
<div className={[classes["product-item"], classes[stylesFor]].join(' ')}>
<img className={[classes["product-img"], classes[stylesFor]].join(' ')} src={product.img} onLoad={notifyImageLoad} />
<div className={[classes["product-desc-container"], classes[stylesFor]].join(' ')}>
<h2 className={[classes["product-title"], classes[stylesFor]].join(' ')}>{product.title}</h2>
<h2 className={[classes["product-desc"], classes[stylesFor]].join(' ')}>Something about product</h2>
<h2 className={[classes["product-price"], classes[stylesFor]].join(' ')}>Price: {product.price}$</h2>
<button onClick={addItem} className={[classes["add-button"], classes[stylesFor]].join(' ')}>ADD TO CART</button>
</div>
</div>
)
}
Routes in the App:
const App = () => {
return (
<>
<Header />
<Routes>
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<HomePage />} />
<Route path="/category/:name" element={<CategoryPage />} />
<Route path="/cart" element={<CartPage />} />
<Route path="*" element={<h1>Error 404 page not found</h1>} />
</Routes>
</>
);
}
I used name const from useParams
to catch when the route param changes so I can reset the state value from useEffect
as shown there, but when I move from /category/:nameX
to the non-category page and then /category/:nameY
, the state still remains same as it was at /category/:nameX
.
To fix your problem, you need a clean-up function in your useEffect
where you are calling dispatch
, to reset your state/store before the component gets unmounted:
const CategoryPage = () => {
const { name } = useParams();
const loading = useSelector((state) => state.shop.loading);
const categoryItems = useSelector((state) => state.shop.categoryItems);
const dispatch = useDispatch();
//counting loaded images that should be shown in category page
const [loadedImages, setLoadedImages] = useState(0);
useEffect(() => {
dispatch(getShopData(`categories/${name}/products`, "category"));
// Here is your clean-up function 👈🏽
return () => {
dispatch(/* set shop.categoryItems to an empty array */);
dispatch(/* set shop.loading to true*/);
setLoadedImages(0);
};
}, [dispatch, name]);
return (
<>
<img src={bg} className={classes["bg"]} />
{categoryItems.length > 0 && (
<div className={classes["container"]}>
{categoryItems.map((product, id) => (
<CategoryItem key={id} product={product} setLoadedImages={setLoadedImages} />
))}
</div>
)}
{(loading || loadedImages != categoryItems.length) && (
<div className="page-load-wrapper">
<Loader manualStyles={{ width: "75px", height: "75px" }} />
</div>
)}
</>
);
};