I have an Index.js where I am getting data via async function and sending data to ProductList Component and from ProductList Component I am sending an id onclick of product Image and then I get Product Detail Page a Single Product. I get Single Product with no issues but when I refresh then I get an error in console saying - Cannot read properties of undefined (reading 'filter') I have used REDUX.
INDEX.JS
import React, { useState, useEffect } from "react";
// import PageNotFound from "../other/page-not-found/PageNotFound";
import ProductList from "./product-list/ProductList";
import ProductFilter from "./product-filter/ProductFilter";
import ProductSearch from "./product-search/ProductSearch";
import Cart from "./cart/Cart";
// import MobileFilterModal from "./mobile-filter-modal/MobileFilterModal";
import { useDispatch, useSelector } from "react-redux";
import { setProductListData } from "../../redux/actions/product-list-actions/ProductListActions";
import Loading from "../other/loading/Loading";
import Skeleton from "../other/skeletons/Skeleton";
// import PageNotFound from "../other/page-not-found/PageNotFound";
const Index = () => {
const [isLoading, setIsLoading] = useState(true);
const dispatch = useDispatch();
const filters = useSelector(
(state) => state.productList.productListData.filters
);
const products = useSelector(
(state) => state.productList.productListData.products
);
// console.log(products);
useEffect(() => {
getProductsData();
}, []);
const getProductsData = async () => {
try {
const response = await fetch("http://localhost:8000/productListing");
const data = await response.json();
if (data) {
setIsLoading(false);
dispatch(setProductListData(data));
} else {
setIsLoading(true);
}
} catch (error) {
console.log(error);
}
};
// if (isLoading) {
// return <Loading loadingProductList={"products"} />;
// }
return (
<>
{filters && products && (
<main>
<div className="dvMain">
<div className="container-fluid">
<div className="row">
<div className="dvFilters col-lg-3 col-xl-2 d-none d-lg-block text-right">
<div className="sticky-top" style={{ top: "90px" }}>
{isLoading
? filters.map((filter) => {
return (
<Skeleton key={filter.id} filterId={filter.id} />
);
})
: filters &&
filters.map((filter) => {
return <ProductFilter key={filter.id} {...filter} />;
})}
</div>
</div>
<div className="dvProducts col-lg-6 col-xl-8">
<div className="row">
<div className="dvSearch col-sm-9 col-md-10 col-lg-12 mb-3">
{isLoading ? (
<Skeleton productSearch={"productSearch"} />
) : (
<ProductSearch />
)}
</div>
<div className="dvFilterMobile col-sm-3 col-md-2 d-lg-none text-center text-sm-right mb-3">
<a
href=""
className="btn bg-light w-100"
data-toggle="modal"
data-target="#mobileFiltersModal"
>
<i className="fa fa-filter"></i>Filter
</a>
</div>
</div>
<div className="row">
{isLoading
? products.map((product) => {
return (
<Skeleton key={product.id} productId={product.id} />
);
})
: products &&
products.map((product) => {
return <ProductList key={product.id} {...product} />;
})}
</div>
</div>
{isLoading ? <Skeleton cart={"cart"} /> : <Cart />}
</div>
</div>
</div>
</main>
)}
</>
);
};
export default Index;
PRODUCTLIST.JS
import React from "react";
import { Link } from "react-router-dom";
import "./productlist.css";
const ProductList = ({ id, img, pack, price, size, heading, description }) => {
// console.log(id, img, pack, price, size, heading, description);
// console.log(id, img, pack, price, size, heading, description);
return (
<>
<div className="col-6 col-md-4 col-lg-6 col-xl-3 mb-4">
<div className="border border-light shadow-sm p-1 h-100">
<div className="image">
<p className="packs">pack of 5</p>
<div className="bg-light text-center pt-2 pb-2 mb-1">
<Link className="d-inline-block" to={`/${id}`}>
<img src={img} className="img-fluid" alt={heading} />
</Link>
</div>
<h5 className="heading-5 text-center">{heading}</h5>
</div>
<div className="description d-flex justify-content-between mb-1">
<div className="paragraph">
<p>{size}</p>
</div>
<div className="paragraph mr-2">
<span>
<i className="fa fa-inr"></i>
<span>{price}</span>
</span>
</div>
</div>
<div className="addBtn text-center">
<button className="btn btn-white w-100" href="detail.html">
Add to Bag
</button>
</div>
</div>
</div>
</>
);
};
export default ProductList;
PRODUCTDETAIL.JS
import React, { useState, useEffect } from "react";
import Cart from "../cart/Cart";
import "./productdetail.css";
import Loading from "../../other/loading/Loading";
import PageNotFound from "../../other/page-not-found/PageNotFound";
import { Link, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { setSelectedProduct } from "../../../redux/actions/product-detail-actions/ProductDetailActions";
const ProductDetail = () => {
const [isLoading, setIsLoading] = useState(true);
const { id } = useParams();
const dispatch = useDispatch();
const products = useSelector(
(state) => state.productList.productListData.products
);
const singleProduct = useSelector(
(state) => state.singleProduct.singleProduct
);
// console.log(id);
// console.log(products);
// console.log(singleProduct);
useEffect(() => {
getProductId();
}, []);
const getProductId = () => {
window.scrollTo(0, 0);
// localStorage.setItem("setProducts", JSON.stringify(products));
// let getProducts = JSON.parse(localStorage.getItem("setProducts"));
products ? setIsLoading(false) : setIsLoading(true);
let singleProduct = [];
singleProduct = products.filter((product) => {
return product.id === parseInt(id);
});
// console.log(singleProduct);
dispatch(setSelectedProduct(singleProduct));
};
if (isLoading) {
return <Loading loadingProductDetail={"product detail"} />;
}
return (
<>
{singleProduct && (
<main>
<div className="dvMain">
<div className="container">
<div className="row">
{singleProduct ? (
singleProduct.map((product) => {
const {
id,
img,
heading,
description,
category,
price,
size,
} = product;
return (
<div key={id} className="col-lg-9">
<div className="row">
<div className="dvProducts col-12">
<div className="row">
<div className="dvBack col-12">
<Link
to="/products"
className="mb-1 d-inline-block"
>
<i className="fa fa-angle-left"></i>
<span> Back</span>
</Link>
</div>
<div className="col-12">
<div className="row">
<div className="col-12 col-md-6 col-xl-4 mb-3">
<div className="border border-light shadow-sm p-1 h-100 d-flex justify-content-center align-items-center">
<div className="bg-light text-center h-100">
<a className="d-inline-block h-100">
<img
src={img}
className="img-fluid"
alt={heading}
/>
</a>
</div>
</div>
</div>
<div className="col-12 col-md-6 col-xl-8 d-flex mb-3 mb-xl-0">
<div className="m-md-auto">
<div>
<h2 className="heading-2">{heading}</h2>
</div>
<div className="mb-2">
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
</div>
<div className="mb-3">
<p className="paragraph">
{description}
</p>
</div>
<div className="d-flex mb-3">
<div className="mr-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Size:
</h5>
<span className="paragraph d-inline-block">
{size}
</span>
</div>
<div className="mr-2 ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Category:
</h5>
<span className="paragraph d-inline-block">
{category}
</span>
</div>
<div className="ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Price:
</h5>
<span className="paragraph d-inline-block">
<i className="fa fa-inr"></i>
<span className="d-inline-block">
{price}.00
</span>
</span>
</div>
</div>
<div>
<button
className="btn btn-black"
href="/-"
>
Add to Bag
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="dvTabs col-12 mt-4">
<div className="row">
<div className="col-12">
<div className="row">
<div className="col-12">
<ul
className="nav nav-tabs"
id="myTab"
role="tablist"
>
<li className="nav-item">
<a
className="heading-5 nav-link active"
id="description-tab"
data-toggle="tab"
href="#description"
role="tab"
aria-controls="description"
aria-selected="true"
>
Description
</a>
</li>
</ul>
</div>
</div>
<div
className="dvTabsContent tab-content row"
id="myTabContent"
>
<div
className="tab-pane fade show active py-3 col-12"
id="description"
role="tabpanel"
aria-labelledby="description-tab"
>
<div className="row">
<div className="col-12">
<div className="table-responsive">
<table className="table table-striped table-bordered">
<thead>
<tr>
<th
className="heading-5"
scope="col"
>
Details
</th>
<th
className="heading-5"
scope="col"
>
Weight
</th>
<th
className="heading-5"
scope="col"
>
1Glass 250ml
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Amount per serving</td>
<td>250ml</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
})
) : (
<PageNotFound />
)}
<Cart id={id} />
</div>
</div>
</div>
</main>
)}
</>
);
};
export default ProductDetail;
APP.JS
import React from "react";
import "./App.css";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Homepage from "./containers/home-page/index";
import Header from "./containers/header/Header";
import Footer from "./containers/footer/Footer";
// import ProductListing from "./containers/products/product-list/ProductList";
import ProductDetail from "./containers/products/product-detail/ProductDetail";
import PageNotFound from "./containers/other/page-not-found/PageNotFound";
import Account from "./containers/account/index";
import Checkout from "./containers/checkout/Index";
import LoginModal from "./containers/auth/modal/login-modal/LoginModal";
import SignupModal from "./containers/auth/modal/signup-modal/SignupModal";
import MobileCartModal from "./containers/products/mobile-cart-modal/MobileCartModal";
import Index from "./containers/products";
const App = () => {
return (
<>
<BrowserRouter>
<Header />
<main>
<Switch>
<Route path="/" exact component={Homepage} />
<Route path="/products" component={Index} />
<Route path="/:id" children={<ProductDetail />} />
<Route path="/checkout" component={Checkout} />
<Route path="/account" component={Account} />
<Route path="*" component={PageNotFound} />
</Switch>
</main>
<Footer />
<LoginModal />
<SignupModal />
<MobileCartModal />
</BrowserRouter>
</>
);
};
export default App;
PRODUCTDETAILACTIONS.JS
export const setSelectedProduct = (product) => {
return {
type: "SET_SELECTED_PRODUCT",
payload: product,
};
};
PRODUCTDETAILREDUCER.JS
const initialState = {
singleProduct: [],
};
export const productDetailReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_SELECTED_PRODUCT":
return {
...state,
singleProduct: action.payload,
};
default:
return state;
}
};
REDUCER INDEX.JS
import { combineReducers } from "redux";
import { aboutUsReducer } from "./aboutus-reducers/AboutUsReducers";
import { footerReducer } from "./footer-reducers/FooterReducer";
import { headerReducer } from "./header-reducers/headerReducer";
import { mainSliderReducer } from "./homepage-reducers/mainSliderReducer";
import { youtubeReducer } from "./homepage-reducers/YoutubeReducer";
import { productDetailReducer } from "./product-detail/ProductDetailReducer";
import { productListReducer } from "./product-list-reducers/ProductListReducer";
import { productSliderData } from "./productslider-reducers/ProductSliderReducer";
import { teamReducer } from "./team-reducers/TeamReducers";
const rootReducer = combineReducers({
header: headerReducer,
mainSlider: mainSliderReducer,
youtube: youtubeReducer,
aboutUs: aboutUsReducer,
productSlider: productSliderData,
team: teamReducer,
footer: footerReducer,
productList: productListReducer,
singleProduct: productDetailReducer,
});
export default rootReducer;
STORE.JS
import { createStore } from "redux";
import rootReducer from "./reducers/index";
const store = createStore(
rootReducer,
{},
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
PRODUCTLISTACTIONS.JS
export const setProductListData = (data) => {
return {
type: "SET_PRODUCT_LIST_DATA",
payload: data,
};
};
PRODUCTLISTREDUCER.JS
const initialState = {
productListData: {},
};
export const productListReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_PRODUCT_LIST_DATA":
return {
...state,
productListData: action.payload,
};
default:
return state;
}
};
DB.JSON I am fetching productListing from json file which look like this
CodePudding user response:
From what I can see, the ProductDetails
component is missing a dependency on the id
route param to select the correct products to filter, and I am assuming that since the index file is fetching and populating the store each time it mounts that perhaps the initial products state is null, undefined, or some other non-array value that is missing the filter
method.
Add the missing id
route param to the useEffect
hook's dependency array so products are fetched when the route changes.
const { id } = useParams();
...
useEffect(() => {
getProductId(id);
}, [id]);
Since it appears that products
is potentially undefined, i.e. falsey, you should account for then when attempting to filter the array. Provide a fallback empty array to filter from if products
is falsey.
const getProductId = (id) => {
window.scrollTo(0, 0);
products ? setIsLoading(false) : setIsLoading(true);
const singleProduct = (products || []).filter((product) => {
return product.id === parseInt(id);
});
dispatch(setSelectedProduct(singleProduct));
};
You didn't include any of your Redux code (i.e. actions/reducers/store configuration) but a suggestion is to always provide a consistent state invariant. For example, always returning an array of products
and using the array length and some "isLoading" state to determine when it's safe to filter the state. At a minimum, if it's guaranteed that products
is always an array then all array methods will always work as expected, regardless of where the app is in its lifecycle and loading data/state.
CodePudding user response:
Hey I found this solution. I fetched products through async function instead of getting products back from useSelector. Please check ProductDetail Component below.
PRODUCTDETAIL.JS
import React, { useState, useEffect } from "react";
import Cart from "../cart/Cart";
import "./productdetail.css";
import Loading from "../../other/loading/Loading";
import PageNotFound from "../../other/page-not-found/PageNotFound";
import { Link, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { setSelectedProduct } from "../../../redux/actions/product-detail-actions/ProductDetailActions";
const ProductDetail = () => {
const [loading, setLoading] = useState(true);
const { id } = useParams();
const dispatch = useDispatch();
const singleProduct = useSelector(
(state) => state.singleProduct.singleProduct
);
useEffect(() => {
getProductListingData();
}, []);
const getProductListingData = async () => {
try {
const response = await fetch("http://localhost:8000/productListing");
const data = await response.json();
if (data) {
setLoading(false);
getProductID(data.products);
} else {
// setProducts("PRODUCT LISTING DATA NOT FOUND");
setLoading(true);
console.log("PRODUCT LISTING DATA NOT FOUND");
}
} catch (error) {
console.log(error);
}
};
const getProductID = (products) => {
window.scroll(0, 0);
let singleProduct = [];
singleProduct = products.filter((item) => {
return item.id === parseInt(id);
});
dispatch(setSelectedProduct(singleProduct));
};
if (loading) {
return <Loading loadingProductDetail="Loading Product Details" />;
}
console.log(id);
console.log(singleProduct);
return (
<>
<main>
<div className="dvMain">
<div className="container">
<div className="row">
{singleProduct.length > 0 &&
singleProduct.map((product) => {
const {
id,
img,
heading,
description,
pack,
price,
size,
category,
detailsTable,
} = product;
return (
<div key={id} className="col-lg-9">
<div className="row">
<div key={id} className="dvProducts col-12">
<div className="row">
<div className="dvBack col-12">
<Link
to="/products"
className="mb-1 d-inline-block"
>
<i className="fa fa-angle-left"></i>
<span> Back</span>
</Link>
</div>
<div className="col-12">
<div className="row">
<div className="col-12 col-md-6 col-xl-4 mb-3">
<div className="border border-light shadow-sm p-1 h-100 d-flex justify-content-center align-items-center">
<div className="bg-light text-center h-100">
<a className="d-inline-block h-100">
<img
src={img}
className="img-fluid"
alt={heading}
/>
</a>
</div>
</div>
</div>
<div className="col-12 col-md-6 col-xl-8 d-flex mb-3 mb-xl-0">
<div className="m-md-auto">
<div>
<h2 className="heading-2">{heading}</h2>
</div>
<div className="mb-2">
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
</div>
<div className="mb-3">
<p className="paragraph">{description}</p>
</div>
<div className="d-flex mb-3">
<div className="mr-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Pack:
</h5>
<span className="paragraph d-inline-block">
{pack}
</span>
</div>
<div className="mr-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Size:
</h5>
<span className="paragraph d-inline-block">
{size}
</span>
</div>
<div className="mr-2 ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Category:
</h5>
<span className="paragraph d-inline-block">
{category}
</span>
</div>
<div className="ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Price:
</h5>
<span className="paragraph d-inline-block">
<i className="fa fa-inr"></i>
<span className="d-inline-block">
{price}.00
</span>
</span>
</div>
</div>
<div>
<button
className="btn btn-black"
href="/-"
>
Add to Bag
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="dvTabs col-12 mt-4">
<div className="row">
<div className="col-12">
<div className="row">
<div className="col-12">
<ul
className="nav nav-tabs"
id="myTab"
role="tablist"
>
<li className="nav-item">
<a
className="heading-5 nav-link active"
id="description-tab"
data-toggle="tab"
href="#description"
role="tab"
aria-controls="description"
aria-selected="true"
>
Description
</a>
</li>
</ul>
</div>
</div>
<div
className="dvTabsContent tab-content row"
id="myTabContent"
>
<div
className="tab-pane fade show active py-3 col-12"
id="description"
role="tabpanel"
aria-labelledby="description-tab"
>
<div className="row">
<div className="col-12">
<div className="table-responsive">
<table className="table table-striped table-bordered">
<thead>
<tr>
<th
className="heading-5"
scope="col"
>
Details
</th>
<th
className="heading-5"
scope="col"
>
Weight
</th>
<th
className="heading-5"
scope="col"
>
1Glass 250ml
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Amount per serving</td>
<td>250ml</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
})}
<Cart id={id} />
</div>
</div>
</div>
</main>
</>
);
};
export default ProductDetail;