본문 바로가기
Coding/Redux

Redux - Pagnigation(with redux-toolkit and nextjs)

by z쿳쿳z 2023. 2. 9.
728x90
반응형

많은 양의 데이터가 있으면 페이지를 나누어서 데이터를 보여주면 된다. 이런 페이지 이동을 할 수 있도록 page list를 page + navigation이 합쳐서 Pagnigation 이라고 부른다.

# object

   - 페이지 리스트 만들기

   - 뒤로가기 했을때, 이전 페이지 상태유지

 

페이지를 이동할 때 마다 backend로 query를 같이 보내서 어떤 페이지인지 몇개의 데이터를 원하는지 보내야한다. 이렇게 query로 보내고 난 후 Url도 같이 이동해주면 이전에 어떤 페이지 항목을 보고 있었지알 수 있다.

ex)

   * page_no는 현재 몇페이지를 보여줄지

   * offset은 몇개의 데이터를 받기를 원하는지

http://localhost:3000/api/node/page_no=1&offset=10

 

## 페이지 리스트 만들기

pagnigation을 만들기 위해서는 우선 받아오는 list의 총 수를 알고 있어야 한다. 그리고 한 페이지당 몇개씩을 보여줄지를 알고 있어야한다. 한 페이지에 몇개씩 보여줄지 결정하는 query는 offset으로 보낸다.

 

아래 코드는 페이지 리스트를 만드는 로직이다.

   *totalCount는 api 호출에서 오는 list의 총 수

   *몇개를 보여줄지 offset으로 나누면 총 page count가 나온다. Math.ceil로 무조건 올림을 하는 이유는 만약 list수가 59개이면 5페이지하고 6번째 페이지에 보여줄게 9개가 남기 때문이다.

   * range 함수에서 pageNo는 현재 보고 있는 pageNo 이다. 페이지가 1~30까지 있는데 pagnigation은 1~10까지 보여 주고 있으면 1이고 11~20까지 보여주고 있으면 pageNo는 2 이고, 21~30까지 보여주고 있으면 3이 된다.

   * pageNo가 1이면 페이지 자릿수(digit 변수에)를 1로 넣어준다

   * 1이 아닐때는 시작하는 숫자를 넣어줘야한다. pageNo가 2면 pagnigation의 시작 숫자는 11이 된다.

   * totalPageCount가 10보다 작거나 같으면 다음 페이지 리스트, 즉 pageNo가 1이기 때문에  1~10 이내로 보여주면 된다.

   * Array.from({length: number}, (_, idx) => idx + digit)) 함수는 length 길이만큼 배열을 만들어 주고, 두번째 인자 콜백에서 어떤 element를 넣을지 정해준다.

   *Array.from({ length: 10 }, (_, idx) => idx + 1); 의 결과 값은 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 이다.

const totalPageCount = Math.ceil(totalCount / offset);
export const range = (pageNo: number, totalPageCount: number) => {
  let digit = 0;
  let maxPage = Math.ceil(totalPageCount / 10);

  if (pageNo === 1) {
    digit = pageNo;
  } else {
    digit = (pageNo - 1) * 10 + 1;
  }

  if (totalPageCount <= 10) {
    return Array.from({ length: totalPageCount }, (_, idx) => idx + digit);
  }

  if (maxPage === pageNo) {
    let length = totalPageCount % 10;
    if (length === 0) {
      return Array.from({ length: 10 }, (_, idx) => idx + digit);
    } else {
      return Array.from({ length }, (_, idx) => idx + digit);
    }
  } else {
    return Array.from({ length: 10 }, (_, idx) => idx + digit);
  }
};

이렇게 페이지 리스트를 만들어 주고, 페이지를 이동할 때마다 api를 호출을 해야한다.

   * 페이지 숫자를 클릭하면 current page 값만 업데이트 시킨다(clickPage 함수 실행)

   * 싱글 애로우를 누르면 다음 페이지 리스트로 넘가기 때문에 current page 와 pageNo도 업데이트 시킨다(clickNext or clickPrev 함수 실행)

   * 더블 애로우를 누르면 제일 처음 페이지 이거나 제일 마지막 페이지로 이동을 시켜야 하므로 current page와 pageNo도 업데이트 시킨다(clickFirst or clickLast 함수 실행)

 

const Pagnigation = ({
  totalCount,
  pageName,
  pageNo,
  offset,
  currentPage,
  startDate,
  endDate,
}: IPagnigationProps) => {
  const dispatch = useDispatch<AppDispatch>();
  const [pageList, setPageList] = useState<number[]>([]);
  const totalPageCount = Math.ceil(totalCount / offset);
  const maxPage = Math.ceil(totalPageCount / 10);
  const router = useRouter();

  useEffect(() => {
    setPageList(range(pageNo, totalPageCount));
  }, [pageNo, totalPageCount]);

  const clickPage = async (e: React.MouseEvent<HTMLDivElement>, page: number) => {
    e.preventDefault();
    if (pageName === 'transaction') {
      if (account) {
        router.push(`?transaction&page_no=${page}`);
        dispatch(setTransactionCurrentPage(page));
      }
    }
  };

  const clickNext = async () => {
    const searchText = typeof router.query.id === 'string' ? router.query.id : '';
    if (currentPage === totalPageCount) {
      return;
    }

    if (pageNo === totalPageCount) {
      return;
    }

    if (pageNo === maxPage) {
      return;
    }

    if (pageName === 'transaction') {
      dispatch(setTransactionPage((pageNo + 1) * 10 - 9));
      router.push(`?page_no=${(pageNo + 1) * 10 - 9}`);
      dispatch(setTransactionCurrentPage(pageNo + 1));
    }
  };

  const clickPrev = async () => {
    const searchText = typeof router.query.id === 'string' ? router.query.id : '';
    if (currentPage === 1) {
      return;
    }
    if (pageNo === 1) {
      return;
    }
    if (pageName === 'transaction') {
      dispatch(setTransactionPage((pageNo - 1) * 10));
      router.push(`?page_no=${(pageNo - 1) * 10}`);
      dispatch(setTransactionCurrentPage(pageNo - 1));
    }
  };

  const clickFirst = async () => {
    const searchText = typeof router.query.id === 'string' ? router.query.id : '';

    if (currentPage === 1) {
      return;
    }

    if (pageName === 'transaction') {
      dispatch(setTransactionPage(1));
      router.push(`?page_no=${1}`);
      dispatch(setTransactionCurrentPage(1));
    } 
  };

  const clickLast = async () => {
    const searchText = typeof router.query.id === 'string' ? router.query.id : '';
    if (currentPage === totalCount) {
      return;
    }

    if (pageNo === totalPageCount) {
      return;
    }

    if (pageName === 'transaction') {
      dispatch(setTransactionPage(totalPageCount));
      router.push(`?page_no=${totalPageCount}`);
      dispatch(setTransactionCurrentPage(maxPage));
    }
  };

  return (
    <Container>
      {maxPage > 1 && (
        <>
          <Image src={'/images/first.png'} alt={''} width={20} height={20} onClick={clickFirst} />
          <Image src={'/images/prev.png'} alt={''} width={20} height={20} onClick={clickPrev} />
        </>
      )}
      {pageList &&
        pageList.map((value, index) => {
          if (value === currentPage) {
            return <ClickedContent key={`${index}`}>{value}</ClickedContent>;
          } else {
            return (
              <Content key={`${index}`} onClick={(e) => clickPage(e, value)}>
                {value}
              </Content>
            );
          }
        })}
      {maxPage > 1 && (
        <>
          <Image src={'/images/next.png'} alt={''} width={20} height={20} onClick={clickNext} />
          <Image src={'/images/last.png'} alt={''} width={20} height={20} onClick={clickLast} />
        </>
      )}
    </Container>
  );
};

export default Pagnigation;

 

 

## Next js 에서 뒤로가기 했을 때, 이전페이지 유지 하는 법

ux적으로 이전에 보던 페이지를 알고 있으면 좋을 것이다. 1페이지를 보다가 갑자기 5페이지로 이동하고 3페이지로 이동하거나 뒤로가기 버튼을 눌렀을 때, 이전에 봤었던 5페이지를 보여주는게 자연스럽다. Next js의 강점은 SSR이고, 이전에 페이지로 이동할 때 url을 서버에서 미리알 수 있기에 이전 페이지를 보여주는데 용이하다

export const getServerSideProps = wrapper.getServerSideProps((store)=>(context)=> {
    const {query} = context;
    let initQuery = {
      page_no: 1,
      offset: 10,
    };

    if (!isEmptyObj(query)) {
      initQuery.page_no = query.page_no && Number(query.page_no) ? Number(query.page_no) : 1;
      let digit = Number(initQuery.page_no) / Number(initQuery.offset);
      let rest = Number(initQuery.page_no) % Number(initQuery.offset);
      const pages = rest ? parseInt(String(digit)) + 1 : parseInt(String(digit));
      store.dispatch(setTransactionPage(pages));
      store.dispatch(setTransactionCurrentPage(initQuery.page_no));
    }
    await store.dispatch(
      fetchTransaction({
        baseUrl: SERVER_SIDE_BASE_URL,
        query: initQuery,
        headers,
      })
    );
}
728x90
반응형