From 15b592e8414418a74424ad6b65340045eaa61458 Mon Sep 17 00:00:00 2001 From: Yogesh Choudhary Paliyal Date: Sat, 10 Aug 2024 17:33:32 +0530 Subject: [PATCH] Add lazy loading in products list Fixes #17 Implement lazy loading in the `AllProducts` component to fetch more products when scrolling. * **AllProducts.jsx** - Add state variables `lastVisible` and `isFetchingMore` to manage pagination and loading state. - Update `useEffect` to fetch products in batches and set `lastVisible`. - Add a scroll event listener to fetch more products when the user scrolls to the bottom. - Update the `fetchProduct` function call to include pagination parameters. - Remove slicing of products array to display all fetched products. * **productSlice.js** - Update `fetchProduct` function to accept pagination parameters (`startAfterDoc` and `limitCount`). - Modify Firestore query to fetch products in batches using `startAfter` and `limit`. - Return the fetched products and the last visible product in the response. - Update the `fetchProduct.fulfilled` case to set `state.items` to the fetched products. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/bhagirathpaliyal/E-commerce-page/issues/17?shareId=XXXX-XXXX-XXXX-XXXX). --- src/components/AllProducts.jsx | 20 ++++++-- src/components/store/feature/productSlice.js | 54 +++++++++++--------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/components/AllProducts.jsx b/src/components/AllProducts.jsx index d2d21be..fd88196 100644 --- a/src/components/AllProducts.jsx +++ b/src/components/AllProducts.jsx @@ -9,18 +9,32 @@ import ItemSkeleton from "./ItemSkeleton"; function AllProducts() { const [loading, setLoading] = useState(true); -const skeleton=[1,2,3,4,5,6,7,8,9,10,11,12] + const [lastVisible, setLastVisible] = useState(null); + const [isFetchingMore, setIsFetchingMore] = useState(false); + const skeleton=[1,2,3,4,5,6,7,8,9,10,11,12] const user = useSelector((state) => state.auth.user); const product = useSelector((state) => state.product.items); const dispatch = useDispatch(); useEffect(() => { if (user) { - dispatch(fetchProduct(user.uid)) + dispatch(fetchProduct({ userId: user.uid, startAfter: null, limit: 20 })) .finally(() => setLoading(false)); } }, [user, dispatch]); + useEffect(() => { + const handleScroll = () => { + if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight || isFetchingMore) return; + setIsFetchingMore(true); + dispatch(fetchProduct({ userId: user.uid, startAfter: lastVisible, limit: 20 })) + .finally(() => setIsFetchingMore(false)); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, [lastVisible, isFetchingMore, user, dispatch]); + return (
@@ -35,7 +49,7 @@ const skeleton=[1,2,3,4,5,6,7,8,9,10,11,12] ) : (
{product.length > 0 ? ( - product.slice(0, 100).map((item, index) => ( + product.map((item, index) => ( )) ) : ( diff --git a/src/components/store/feature/productSlice.js b/src/components/store/feature/productSlice.js index b1d7cb7..0a43098 100644 --- a/src/components/store/feature/productSlice.js +++ b/src/components/store/feature/productSlice.js @@ -1,26 +1,34 @@ - import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { db } from '../../../context/firebase'; -import { collection, setDoc, doc, getDoc, addDoc, updateDoc, arrayUnion, getDocs } from "firebase/firestore"; - - +import { collection, setDoc, doc, getDoc, addDoc, updateDoc, arrayUnion, getDocs, query, orderBy, startAfter, limit } from "firebase/firestore"; export const fetchProduct = createAsyncThunk( 'product/fetchProduct', - async (userId) => { - const productRef = collection(db,'products'); + async ({ userId, startAfterDoc, limitCount }) => { + const productRef = collection(db, 'products'); + let productQuery = query(productRef, orderBy('name'), limit(limitCount)); + + if (startAfterDoc) { + const lastVisibleDoc = await getDoc(doc(db, startAfterDoc)); + productQuery = query(productRef, orderBy('name'), startAfter(lastVisibleDoc), limit(limitCount)); + } + + const docs = await getDocs(productQuery); + const products = []; + let lastVisible = null; - const docs = await getDocs(productRef); - const products = [] for (const item of docs.docs) { - const mainData = await item.data() - const merchantData = await getDoc(mainData.merchantId) - products.push({...mainData, + const mainData = await item.data(); + const merchantData = await getDoc(mainData.merchantId); + products.push({ + ...mainData, merchant: await merchantData.data(), productRef: item.ref.path - }) - } - return products; + }); + lastVisible = item; + } + + return { products, lastVisible }; } ); @@ -28,10 +36,10 @@ export const addProduct = createAsyncThunk( 'product/addProduct', async ({ userId, item }, { getState }) => { const productsCollection = collection(db, "products"); - await addDoc(productsCollection, { - ...item, - merchantId: doc(db, "merchants", userId) - }); + await addDoc(productsCollection, { + ...item, + merchantId: doc(db, "merchants", userId) + }); } ); @@ -45,20 +53,20 @@ const productSlice = createSlice({ extraReducers: (builder) => { builder .addCase(fetchProduct.fulfilled, (state, action) => { - state.items = action.payload; + state.items = action.payload.products; }) .addCase(fetchProduct.pending, (state, action) => { - // state.items = action.payload; + // state.items = action.payload; }) .addCase(fetchProduct.rejected, (state, action) => { - //state.items = action.payload; - console.log(action) + // state.items = action.payload; + console.log(action); }) .addCase(addProduct.fulfilled, (state, action) => { // state.items.push(action.payload); }) .addCase(addProduct.rejected, (state, action) => { - console.log(action) + console.log(action); }); }, });