javascriptreactjsnext.js

On scroll pagination is not working on big screen


I have implemented a on scroll pagination. but this is not working on large screen. for my api i have limit 12, offset and total. for the first time load on the large screen there is enough space for the 12 dreams, so there is no scroll. that's why the pagination is not working there.

here is my sharedDream.tsx file, where i have added this on scroll pagination.

'use client';

import React, { useCallback, useEffect, useState } from 'react';
import { message, Skeleton } from 'antd';
import Image from 'next/image';
import { useRouter } from 'next/navigation';

import NoDreamIcon from '@/assests/svg/NoDreamIcon';
import { useAppSelector } from '@/hooks/reduxHooks';
import { selectpagename } from '@/redux/slice/pageTitleSlice';
import { fetchAllMyActivityDreams } from '@/service/shared-dreams/my-activity';
import { fetchAllSharedDreams } from '@/service/shared-dreams/shared-dreams';

import { IDreamDetails } from '../../../@types/common';
import testImage from '../../../public/shared-dream.png';

const SharedDreams = () => {
  const router = useRouter();
  const { activeTab } = useAppSelector(selectpagename);
  const [sharedDreamsList, setSharedDreamsList] = useState<IDreamDetails[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const limit = 12;
  const [offset, setOffset] = useState(0);
  const [hasMore, setHasMore] = useState<boolean>(false);
  const [myOffset, setMyOffset] = useState(0);
  const [isLoadingMore, setIsLoadingMore] = useState<boolean>(false);

  const getAllDreams = async () => {
    setIsLoading(true);
    const response =
      activeTab === 'My Activity'
        ? await fetchAllMyActivityDreams(limit, myOffset)
        : await fetchAllSharedDreams(limit, offset, '');
    if (response?.status === 1) {
      const newData = response?.data ?? [];
      if (activeTab === 'My Activity') {
        setMyOffset(limit);
      } else {
        setOffset(limit);
      }
      setSharedDreamsList((prevData) => [...prevData, ...newData]);
      if (
        response?.total >
        (sharedDreamsList?.length || 0) + (newData?.length || 0)
      ) {
        setHasMore(true);
      } else {
        setHasMore(false);
      }
    } else {
      message.error(response?.message ?? 'Something went wrong');
    }
    setIsLoading(false);
  };

  useEffect(() => {
    getAllDreams();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const handleScroll = useCallback(() => {
    const scrollPosition = window.scrollY + window.innerHeight;
    const docElement = document.documentElement;
    const { scrollHeight } = docElement ?? {};

    if (
      scrollHeight &&
      scrollPosition >= scrollHeight - 1 &&
      !isLoadingMore &&
      hasMore
    ) {
      setIsLoadingMore(true);
      getAllDreams().finally(() => {
        setIsLoadingMore(false);
        if (activeTab === 'My Activity') {
          setMyOffset(limit + offset);
        } else {
          setOffset(limit + offset);
        }
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [hasMore, isLoadingMore]);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, [handleScroll]);

  const handleClick = (id: string) => {
    router.push(`/shared-dreams/${id}`);
  };

  return (
    <div className='flex h-full w-full flex-col gap-4 text-white'>
      <h1 className='text-base font-bold'>
        {activeTab === 'My Activity'
          ? 'Shared Dreams I’m in'
          : 'All Shared Dreams'}
      </h1>
      {sharedDreamsList?.length > 0 || isLoading ? (
        <div className='grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
          {sharedDreamsList?.map((dream: IDreamDetails, index: number) => {
            const {
              thumbnail,
              title,
              total_member: totalMember,
              is_accessible: isAccessible = true
            } = dream || {};
            return (
              <div
                className={`flex flex-col gap-2 ${!isAccessible && 'cursor-not-allowed'}`}
                onClick={() => isAccessible && handleClick(dream?.id)}
                role='button'
                tabIndex={0}
                key={Number(index)}
              >
                <Image
                  src={
                    thumbnail
                      ? `${thumbnail?.base_url}${thumbnail?.internal_path}${thumbnail?.image}`
                      : testImage
                  }
                  alt={`Dream ${index + 1}`}
                  className={`aspect-[4/3] h-[180px] w-full rounded-lg object-cover ${!isAccessible && 'opacity-20'}`}
                  height={122}
                  width={260}
                />
                <div className='flex flex-col gap-1'>
                  <p className='truncate text-base font-semibold'>{title}</p>
                  <p>
                    <span className='font-bold'>{totalMember}</span>{' '}
                    <span className='text-neutrals-300'>Members</span>
                  </p>
                </div>
              </div>
            );
          })}
          {(isLoading || isLoadingMore) &&
            Array.from({ length: 8 }).map((_, index) => (
              // eslint-disable-next-line react/no-array-index-key
              <div className='flex flex-col gap-2' key={index}>
                <Skeleton.Image
                  active
                  rootClassName='skeleton-img skeleton-wrapper'
                />
                <Skeleton
                  active
                  paragraph={{ rows: 1 }}
                  rootClassName='skeleton-wrapper skeleton-card'
                />
              </div>
            ))}
        </div>
      ) : (
        !isLoading &&
        sharedDreamsList?.length === 0 && (
          <div className='flex h-full flex-col items-center justify-center self-stretch'>
            <div className='flex flex-col items-center justify-center gap-3 self-stretch text-center max-sm:h-[77vh]'>
              <NoDreamIcon />
              <div className='inline-flex flex-col items-center justify-center gap-1'>
                <p className='text-lg font-bold leading-[27px] text-[#f6f6fd]'>
                  No Dreams Joined Yet
                </p>
                <p className='self-stretch text-center text-sm font-medium leading-[21px] text-[#9090af]'>
                  {`It looks like you haven't joined any community dreams yet.`}
                </p>
              </div>
            </div>
          </div>
        )
      )}
    </div>
  );
};

export default SharedDreams;

as in the image i have total 18 dreams and there is no scroll for the big screen. so the pagination is not working. can anyone help with this ?

enter image description here


Solution

  • on large screens, when all 12 items fit without scrolling, the scroll-based pagination doesn't trigger. Let's modify the code to handle this case by checking if we need to load more content based on viewport size as well as scroll position.

    import React, { useCallback, useEffect, useState } from 'react';
    import { message, Skeleton } from 'antd';
    import Image from 'next/image';
    import { useRouter } from 'next/navigation';
    
    const SharedDreams = () => {
      const router = useRouter();
      const { activeTab } = useAppSelector(selectpagename);
      const [sharedDreamsList, setSharedDreamsList] = useState<IDreamDetails[]>([]);
      const [isLoading, setIsLoading] = useState<boolean>(false);
      const limit = 12;
      const [offset, setOffset] = useState(0);
      const [hasMore, setHasMore] = useState<boolean>(false);
      const [myOffset, setMyOffset] = useState(0);
      const [isLoadingMore, setIsLoadingMore] = useState<boolean>(false);
    
      const getAllDreams = async () => {
        setIsLoading(true);
        const response =
          activeTab === 'My Activity'
            ? await fetchAllMyActivityDreams(limit, myOffset)
            : await fetchAllSharedDreams(limit, offset, '');
        if (response?.status === 1) {
          const newData = response?.data ?? [];
          if (activeTab === 'My Activity') {
            setMyOffset(limit);
          } else {
            setOffset(limit);
          }
          setSharedDreamsList((prevData) => [...prevData, ...newData]);
          if (
            response?.total >
            (sharedDreamsList?.length || 0) + (newData?.length || 0)
          ) {
            setHasMore(true);
          } else {
            setHasMore(false);
          }
        } else {
          message.error(response?.message ?? 'Something went wrong');
        }
        setIsLoading(false);
      };
    
      const checkIfMoreContentNeeded = useCallback(() => {
        // Get the viewport height
        const viewportHeight = window.innerHeight;
        // Get the content height
        const contentElement = document.documentElement;
        const contentHeight = contentElement.scrollHeight;
        
        // If content height is less than viewport height and we have more items to load
        if (contentHeight <= viewportHeight && hasMore && !isLoadingMore && !isLoading) {
          setIsLoadingMore(true);
          getAllDreams().finally(() => {
            setIsLoadingMore(false);
            if (activeTab === 'My Activity') {
              setMyOffset((prev) => prev + limit);
            } else {
              setOffset((prev) => prev + limit);
            }
          });
        }
      }, [hasMore, isLoadingMore, isLoading, getAllDreams, activeTab, limit]);
    
      const handleScroll = useCallback(() => {
        const scrollPosition = window.scrollY + window.innerHeight;
        const docElement = document.documentElement;
        const { scrollHeight } = docElement ?? {};
    
        if (
          scrollHeight &&
          scrollPosition >= scrollHeight - 1 &&
          !isLoadingMore &&
          hasMore
        ) {
          setIsLoadingMore(true);
          getAllDreams().finally(() => {
            setIsLoadingMore(false);
            if (activeTab === 'My Activity') {
              setMyOffset((prev) => prev + limit);
            } else {
              setOffset((prev) => prev + limit);
            }
          });
        }
      }, [hasMore, isLoadingMore, getAllDreams, activeTab, limit]);
    
      useEffect(() => {
        getAllDreams();
      }, []);
    
      useEffect(() => {
        // Check if we need more content after initial load
        checkIfMoreContentNeeded();
        
        // Add scroll listener
        window.addEventListener('scroll', handleScroll);
        
        // Add resize listener to check if more content is needed when window is resized
        window.addEventListener('resize', checkIfMoreContentNeeded);
    
        return () => {
          window.removeEventListener('scroll', handleScroll);
          window.removeEventListener('resize', checkIfMoreContentNeeded);
        };
      }, [handleScroll, checkIfMoreContentNeeded]);
    
      const handleClick = (id: string) => {
        router.push(`/shared-dreams/${id}`);
      };
    
      return (
        <div className="flex h-full w-full flex-col gap-4 text-white">
          <h1 className="text-base font-bold">
            {activeTab === 'My Activity'
              ? 'Shared Dreams I'm in'
              : 'All Shared Dreams'}
          </h1>
          {sharedDreamsList?.length > 0 || isLoading ? (
            <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
              {sharedDreamsList?.map((dream: IDreamDetails, index: number) => {
                const {
                  thumbnail,
                  title,
                  total_member: totalMember,
                  is_accessible: isAccessible = true
                } = dream || {};
                return (
                  <div
                    className={`flex flex-col gap-2 ${!isAccessible && 'cursor-not-allowed'}`}
                    onClick={() => isAccessible && handleClick(dream?.id)}
                    role="button"
                    tabIndex={0}
                    key={Number(index)}
                  >
                    <Image
                      src={
                        thumbnail
                          ? `${thumbnail?.base_url}${thumbnail?.internal_path}${thumbnail?.image}`
                          : testImage
                      }
                      alt={`Dream ${index + 1}`}
                      className={`aspect-[4/3] h-[180px] w-full rounded-lg object-cover ${!isAccessible && 'opacity-20'}`}
                      height={122}
                      width={260}
                    />
                    <div className="flex flex-col gap-1">
                      <p className="truncate text-base font-semibold">{title}</p>
                      <p>
                        <span className="font-bold">{totalMember}</span>{' '}
                        <span className="text-neutrals-300">Members</span>
                      </p>
                    </div>
                  </div>
                );
              })}
              {(isLoading || isLoadingMore) &&
                Array.from({ length: 8 }).map((_, index) => (
                  <div className="flex flex-col gap-2" key={index}>
                    <Skeleton.Image
                      active
                      rootClassName="skeleton-img skeleton-wrapper"
                    />
                    <Skeleton
                      active
                      paragraph={{ rows: 1 }}
                      rootClassName="skeleton-wrapper skeleton-card"
                    />
                  </div>
                ))}
            </div>
          ) : (
            !isLoading &&
            sharedDreamsList?.length === 0 && (
              <div className="flex h-full flex-col items-center justify-center self-stretch">
                <div className="flex flex-col items-center justify-center gap-3 self-stretch text-center max-sm:h-[77vh]">
                  <NoDreamIcon />
                  <div className="inline-flex flex-col items-center justify-center gap-1">
                    <p className="text-lg font-bold leading-[27px] text-[#f6f6fd]">
                      No Dreams Joined Yet
                    </p>
                    <p className="self-stretch text-center text-sm font-medium leading-[21px] text-[#9090af]">
                      {`It looks like you haven't joined any community dreams yet.`}
                    </p>
                  </div>
                </div>
              </div>
            )
          )}
        </div>
      );
    };
    
    export default SharedDreams;
    
    1. Added a new checkIfMoreContentNeeded function that:
    1. Improved the offset management
    2. Added a resize event listener to handle window size changes