I am having trouble with my react typescript project. This is a simple ecommerce project and I’m trying to fetch data from api and render it but I keep getting this following error. I will paste my code below or you can use the GitHub link here
Error Message
App.tsx
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { HomePage } from "./pages";
import { Provider } from "react-redux";
import store from "./store/store";
const App = () => {
return (
<Provider store={store}>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
</Routes>
</BrowserRouter>
</Provider>
);
};
export default App;
store.ts
import { configureStore } from "@reduxjs/toolkit";
import productsReducer from "./productsSlice";
const store = configureStore({
reducer: {
products: productsReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
productsSlice.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { BASE_URL } from "../utils/apiUrl";
import { STATUS } from "../utils/status";
export type Products = {
id: number;
title: string;
description: string;
price: number;
discountPercentage: number;
rating: number;
stock: number;
brand: string;
category: string;
thumbnail: string;
images: string[];
};
export type ProductsArr = {
limit: number;
products: Products[];
skip: number;
total: number;
};
export type initialTypeState = {
allProducts: ProductsArr;
allProductsStatus: string;
productSingle: Products;
productSingleStatus: string;
};
const initialState: initialTypeState = {
allProducts: {} as ProductsArr,
allProductsStatus: STATUS.IDLE,
productSingle: {} as Products,
productSingleStatus: STATUS.IDLE,
};
const productsSlice = createSlice({
name: "products",
initialState,
reducers: {},
extraReducers(builder) {
builder.addCase(fetchAsyncProducts.pending, (state) => {
state.allProductsStatus = STATUS.LOADING;
});
builder.addCase(fetchAsyncProducts.fulfilled, (state, action) => {
state.allProductsStatus = STATUS.SUCCEEDED;
state.allProducts = action.payload;
});
builder.addCase(fetchAsyncProducts.rejected, (state) => {
state.allProductsStatus = STATUS.FAILED;
});
//single products
builder.addCase(fetchAsyncSingleProducts.pending, (state) => {
state.productSingleStatus = STATUS.LOADING;
});
builder.addCase(fetchAsyncSingleProducts.fulfilled, (state, action) => {
state.productSingleStatus = STATUS.SUCCEEDED;
state.productSingle = action.payload;
});
builder.addCase(fetchAsyncSingleProducts.rejected, (state) => {
state.productSingleStatus = STATUS.FAILED;
});
},
});
export const fetchAsyncProducts = createAsyncThunk(
"products/fetch",
async (limit: number) => {
const response = await fetch(`${BASE_URL}products?limit=${limit}`);
const data = await response.json();
return data;
}
);
export const fetchAsyncSingleProducts = createAsyncThunk(
"product-single/fetch",
async (id: number) => {
const response = await fetch(`${BASE_URL}products/${id}`);
const data = await response.json();
return data;
}
);
export default productsSlice.reducer;
status.ts
export const STATUS = Object.freeze({
IDLE: "IDLE",
FAILED: "FAILED",
LOADING: "LOADING",
SUCCEEDED: "SUCCEEDED",
});
hooks.ts
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "./store";
export const useAppHooks = () => {
const dispatch = useDispatch<AppDispatch>();
const productsState = useSelector((state: RootState) => state.products);
return {
dispatch,
productsState,
};
};
HomePage.tsx
import { useEffect } from "react";
import { HeaderSlider } from "../components";
import { Products, fetchAsyncProducts } from "../store/productsSlice";
import { Loader, ProductList } from "../components";
import { STATUS } from "../utils/status";
import { useAppHooks } from "../store/hooks";
const HomePage = () => {
const { dispatch, productsState } = useAppHooks();
const allProducts = productsState.allProducts.products;
const productStatus = productsState.allProductsStatus;
useEffect(() => {
dispatch(fetchAsyncProducts(40));
}, []);
return (
<main>
<div className="my-2 md:my-8">
<HeaderSlider />
</div>
<div className="main-content h-full bg-gray/30">
<div className="container px-4 mx-auto md:py-8">
<div className="categories py-5">
<div className="categories-item mb-[4.8rem]">
<div className="title-md relative mb-[2.8rem] border-b-2 border-black/10 bg-white py-5 pl-8">
<h3 className="uppercase text-2xl font-semibold text-[rgba(0,0,0,0.4)] ">
See Our Products
</h3>
</div>
{productStatus === STATUS.LOADING ? (
<Loader />
) : (
<ProductList products={allProducts } />
)}
</div>
</div>
</div>
</div>
</main>
);
};
export default HomePage;
ProductList.tsx
import { Products } from "../store/productsSlice";
import { ProductCard } from "./";
type Props = {
products: Products[];
};
const ProductList = ({ products }: Props) => {
return (
<div className="product-list grid my-3">
{products &&
products.length &&
products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
};
export default ProductList;
ProductCard.tsx
import { Link } from "react-router-dom";
import { Products } from "../store/productsSlice";
import { formatPrice, calculateDiscountPrice } from "../utils/formatPrice";
type Props = {
product: Products;
};
const ProductCard = ({ product }: Props) => {
let discountedPrice = calculateDiscountPrice(
product.price,
product.discountPercentage
);
return (
<Link to={`/products/${product.id}`}>
<div className="product-item bg-white border-white hover:border-primary border-2 relative rounded-lg transition-all hover:scale-[1.01]">
<div className="category-product-card absolute left-[-5px] top-2 bg-primary text-white capitalize text-sm py-1 px-4">
{product.category}
</div>
<div className="product-item-img flex justify-center">
<img
className="h-64 overflow-hidden rounded-t-lg"
src={product.thumbnail}
alt={product.title}
/>
</div>
<div className="product-item-info text-base py-3 px-5 text-center">
<div className="brand border-b-2 pb-2 border-black/20">
<span className="mr-1">Brand:</span>
<span className="font-bold">{product.brand}</span>
</div>
<div className="title py-2 capitalize">{product.title}</div>
<div className="price relative">
<span className="old-price opacity-70 line-through text-xs">
{formatPrice(product.price)}
</span>
<span className="new-price mx-1 my-0 font-bold text-base">
{formatPrice(discountedPrice)}
</span>
<span className="discount font-semibold text-[13px] text-primary">
{product.discountPercentage}%
</span>
</div>
</div>
</div>
</Link>
);
};
export default ProductCard;
formatPrice.ts
export const formatPrice = (price: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(price);
};
export const calculateDiscountPrice = (
price: number,
discountPercentage: number
) => {
let discountedPrice = price - price * (discountPercentage / 100);
return discountedPrice;
};
How do I pass the products as a props so React will recognize it?