Search code examples
reactjsreduxreact-hooksreact-reduxpagination

Data is not updated correctly after page changes in Pagination Component


Here I have the pagination component:

import React from 'react';
import classnames from 'classnames';
import { usePagination, DOTS } from './usePagination';
import './pagination.css';
const Pagination = props => {
  const {
    onPageChange,
    totalCount,
    siblingCount = 1,
    currentPage,
    pageSize,
    className
  } = props;

  const paginationRange = usePagination({
    currentPage,
    totalCount,
    siblingCount,
    pageSize
  });

  if (currentPage === 0 || paginationRange.length < 2) {
    return null;
  }

  const onNext = () => {
    onPageChange(currentPage + 1);
  };

  const onPrevious = () => {
    onPageChange(currentPage - 1);
  };

  let lastPage = paginationRange[paginationRange.length - 1];
  return (
    <ul
      className={classnames('pagination-container', { [className]: className })}
    >
      <li
        className={classnames('pagination-item', {
          disabled: currentPage === 1
        })}
        onClick={onPrevious}
      >
        <div className="arrow left" />
      </li>
      {paginationRange.map(pageNumber => {
        if (pageNumber === DOTS) {
          return <li className="pagination-item dots">&#8230;</li>;
        }

        return (
          <li
            className={classnames('pagination-item', {
              selected: pageNumber === currentPage
            })}
            onClick={() => onPageChange(pageNumber)}
          >
            {pageNumber}
          </li>
        );
      })}
      <li
        className={classnames('pagination-item', {
          disabled: currentPage === lastPage
        })}
        onClick={onNext}
      >
        <div className="arrow right" />
      </li>
    </ul>
  );
};

export default Pagination;

And it is integrated in review's page like that:

import React, { useEffect, useState, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import LoadingSpinner from "../Commons/LoadingSpinner";
import FeedCheckFilter from "../Commons/Filters/Filter";
import { getLimitedReviews, updateFilters } from '../../services/FiltersService';
import Review from './Review';
import Pagination from "../Commons/Pagination/Pagination";

let PageSize = 10;

const initialState = {
    limit: 10,
    offset: 0,
    reviews: [],
    count: 0
}

export default function Reviews() {
    const dispatch = useDispatch()
    const token = useSelector((state) => state.user.profile.token) 
    const username = useSelector((state) => state.user.profile.auth)
    const reviews = useSelector((state) => state.reviewsData.reviews) 
    const [currentPage, setCurrentPage] = useState(1);
    const [reviewsState, setReviewsState] = useState(initialState)
    ...
    const loadData = async (queryUrl = filters.url, limit=10, offset=0) => {
        setIsLoading(true)
        // let queryUrl = filters.url;
        if (limit || offset) {
            let action = await getLimitedReviews(token, username, queryUrl || defaultQueryUrl, limit, offset)
            setReviewsState(action.payload)
            dispatch(action)
        }
        setIsLoading(false)
    }

    const currentReviewsData = useMemo(() => {
        const firstPageIndex = (currentPage - 1) * PageSize;
        const lastPageIndex = firstPageIndex + PageSize;
        loadData(filters.url, lastPageIndex, firstPageIndex)
        if (reviews) {
            return reviews
        } else {
            return []
        }
    }, [currentPage]);

    ...
    
    useEffect(() => {
        loadData();
    }, [])
   
    ...

    return (
        <div>
            <h1>Reviews</h1>
            {isLoading ? <LoadingSpinner /> : 
                <>
                    ...
               
                    {currentReviewsData.map(item => {
                        return (<Review key={item.id} review={item} />)
                    })}

                    <Pagination
                        className="pagination-bar"
                        currentPage={currentPage}
                        totalCount={reviewsState.count}
                        pageSize={PageSize}
                        onPageChange={page => setCurrentPage(page)}
                    />
                </>
            }
        </div>
    )
}

The problem is that when I press the button to go from page 1 to page 2, the reviews are updated only after render. So the page remains with the reviews from the previous page. For example if I am in the 5th page, i press the button to go on the 6th. Here on the 6th page I see the reviews from 5th and now if I go to page 7 for example, then I will see the reviews from page 6.

Here I have a hook that I'm using for pagination but I think the problem is not from here:

import React from 'react';
import { useMemo } from 'react';

export const DOTS = '...';

const range = (start, end) => {
  let length = end - start + 1;
  return Array.from({ length }, (_, idx) => idx + start);
};

export const usePagination = ({
  totalCount,
  pageSize,
  siblingCount = 1,
  currentPage
}) => {
  const paginationRange = useMemo(() => {
    const totalPageCount = Math.ceil(totalCount / pageSize);

    // Pages count is determined as siblingCount + firstPage + lastPage + currentPage + 2*DOTS
    const totalPageNumbers = siblingCount + 5;

    /*
      If the number of pages is less than the page numbers we want to show in our
      paginationComponent, we return the range [1..totalPageCount]
    */
    if (totalPageNumbers >= totalPageCount) {
      return range(1, totalPageCount);
    }

    const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
    const rightSiblingIndex = Math.min(
      currentPage + siblingCount,
      totalPageCount
    );

    /*
      We do not want to show dots if there is only one position left 
      after/before the left/right page count as that would lead to a change if our Pagination
      component size which we do not want
    */
    const shouldShowLeftDots = leftSiblingIndex > 2;
    const shouldShowRightDots = rightSiblingIndex < totalPageCount - 2;

    const firstPageIndex = 1;
    const lastPageIndex = totalPageCount;

    if (!shouldShowLeftDots && shouldShowRightDots) {
      let leftItemCount = 3 + 2 * siblingCount;
      let leftRange = range(1, leftItemCount);

      return [...leftRange, DOTS, totalPageCount];
    }

    if (shouldShowLeftDots && !shouldShowRightDots) {
      let rightItemCount = 3 + 2 * siblingCount;
      let rightRange = range(
        totalPageCount - rightItemCount + 1,
        totalPageCount
      );
      return [firstPageIndex, DOTS, ...rightRange];
    }

    if (shouldShowLeftDots && shouldShowRightDots) {
      let middleRange = range(leftSiblingIndex, rightSiblingIndex);
      return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];
    }
  }, [totalCount, pageSize, siblingCount, currentPage]);

  return paginationRange;
};


Solution

  • I think your memo isn't waiting for the result so you always get the result of the previous page loadData is an asynchronous function.

        const currentReviewsData = useMemo(() => {
            const firstPageIndex = (currentPage - 1) * PageSize;
            const lastPageIndex = firstPageIndex + PageSize;
            loadData(filters.url, lastPageIndex, firstPageIndex)
            if (reviews) {
                return reviews
            } else {
                return []
            }
        }, [currentPage]);
    

    In my opinion I don't think you should use a memo for currentReviewsData.

    Try this:

    const [currentReviewsData, setCurrentReviewsData] = useState([]);
    
    useEffect(() => {
      if (typeof currentPage !== "undefined") {
        const firstPageIndex = (currentPage - 1) * PageSize;
        const lastPageIndex = firstPageIndex + PageSize;
        loadData(filters.url, lastPageIndex, firstPageIndex).then((reviews) => {
          setCurrentReviewsData(reviews);
        });
      }
    }, [currentPage]);