GIVEN'S LOG

TIL
dev
2024-03-12 TIL
TIL
#TIL
#next

1. SERVER COMPONENT에서 pathname 접근 법


Next가 13버전 이상부터 server component와 client component 차이가 명확해짐

작업하다보면 pathname이 필요할 때가 많다.


import { usePathname } from "next/navigation";

export default function Page() {
    const pathname = usePathname()
  return <div>RootPage</div>;
}


usePathname이 있지만 이건 client component에서만 사용 가능하다.

그래서 위 코드처럼 쓰면 안됨


해결법: middleware


middleware를 사용하면 서버컴포넌트에서 pathname을 내려 받을 수 있다.


참고: NEXT 공식문서


app 폴더와 같은 directory에 middleware.ts 를 만들어야함


import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-pathname', request.nextUrl.pathname);

  return NextResponse.next({
    request: {
      headers: requestHeaders,
    }
  });
}


이렇게 해놓으면 page route요청시 header에서 pathname을 추출할 수 있다.


코드를 까보면

 const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-pathname", request.nextUrl.pathname);

x-pathname이라는 이름으로 pathname을 지정하고


  return NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });


응답으로 보내줄 수 있다.


2.페이지네이션 코드 refacoty


기존 만들어봤던 페이지네이션 코드


import React, { useEffect, useState } from "react";
import ArrowLeftIcon from "@/components/common/ui/icons/ArrowLeftIcon";
import ArrowRightIcon from "@/components/common/ui/icons/ArrowRightIcon";

type Props = {
  total: number;
  page: number;
  onChange: (page: number) => void;
};

export default function Pagination({ total = 0, page = 1, onChange }: Props) {
  const limit = 5;
  const numPages = Math.ceil(total / 10);

  const [currentPageArray, setCurrentPageArray] = useState<number[]>([]);
  const [totalPageArray, setTotalPageArray] = useState<number[][]>([]);
  useEffect(() => {
    if (page % limit === 1) {
      setCurrentPageArray(totalPageArray[Math.floor(page / limit)]);
    } else if (page % limit === 0) {
      setCurrentPageArray(totalPageArray[Math.floor(page / limit) - 1]);
    }
  }, [page, totalPageArray]);
  useEffect(() => {
    const slicedPageArray = sliceArrayByLimit(numPages, limit);
    setTotalPageArray(slicedPageArray);
    setCurrentPageArray(slicedPageArray[0]);
  }, [numPages]);

  const sliceArrayByLimit = (numPages: number, limit: number) => {
    const totalPageArray = Array(numPages)
      .fill(0)
      .map((_, i) => i);
    return Array(Math.ceil(numPages / limit))
      .fill(0)
      .map(() => totalPageArray.splice(0, limit));
  };

  useEffect(() => {
    if (page % limit === 1) {
      setCurrentPageArray(totalPageArray[Math.floor(page / limit)]);
    } else if (page % limit === 0) {
      setCurrentPageArray(totalPageArray[Math.floor(page / limit) - 1]);
    } else {
      setCurrentPageArray(totalPageArray[Math.floor(page / limit)]);
    }
  }, [page, totalPageArray]);

  return (
    <div className="w-full flex justify-center items-center">
      <div className="pagination flex items-center gap-4">
        <div className="flex items-center">
          <button
            className={
              "flex items-center justify-center w-8 h-8 disabled:text-gray-400"
            }
            onClick={() => {
              onChange(page - 1);
            }}
            disabled={page === 1}
          >
            <ArrowLeftIcon />
          </button>
        </div>
        <div className="flex items-center">
          {currentPageArray?.map((i) => (
            <button
              key={i + 1}
              className={`flex items-center justify-center min-w-[32px] h-8 ${
                page === i + 1
                  ? "text-black dark:text-white font-semibold"
                  : "text-gray-500"
              }`}
              onClick={() => onChange(i + 1)}
            >
              {i + 1}
            </button>
          ))}
        </div>
        <div className="flex items-center">
          <button
            className={
              "flex items-center justify-center w-8 h-8 disabled:text-gray-400"
            }
            onClick={() => onChange(page + 1)}
            disabled={page === numPages || numPages === 0}
          >
            <ArrowRightIcon />
          </button>
        </div>
      </div>
    </div>
  );
}


너무 보기 힘들다고 생각해서 조금 조금씩 리펙토링 일단 중복으로 쓰이는 코드를 줄여봤다.

나는 useEffect를 너무 쓴다... 줄여보자


import React, { useEffect, useState } from "react";
import ArrowLeftIcon from "./icons/ArrowLeftIcon";
import ArrowRightIcon from "./icons/ArrowRightIcon";

type Props = {
  total: number;
  page: number;
  onChange: (page: number) => void;
};

const ITEMS_PER_PAGE = 10;
const PAGINATION_LIMIT = 5;

export default function Pagination({ total, page, onChange }: Props) {
  const numPages = Math.ceil(total / ITEMS_PER_PAGE);
  const [currentPageArray, setCurrentPageArray] = useState<number[]>([]);
  const [totalPageArray, setTotalPageArray] = useState<number[][]>([]);

  const sliceArrayByLimit = (numPages: number, limit: number): number[][] => {
    let pages = Array.from({ length: numPages }, (_, i) => i + 1);
    let result = [];
    for (let i = 0; i < pages.length; i += limit) {
      result.push(pages.slice(i, i + limit));
    }
    return result;
  };

  useEffect(() => {
    const slicedPageArray = sliceArrayByLimit(numPages, PAGINATION_LIMIT);
    setTotalPageArray(slicedPageArray);
    const pageIndex = Math.floor((page - 1) / PAGINATION_LIMIT);
    setCurrentPageArray(slicedPageArray[pageIndex] || []);
  }, [numPages, page]);

  const renderButton = (pageNumber: number, content: React.ReactNode, disabled: boolean) => (
    <button
      className={`flex items-center justify-center w-8 h-8 ${disabled ? "text-gray-400" : ""}`}
      onClick={() => onChange(pageNumber)}
      disabled={disabled}
    >
      {content}
    </button>
  );

  return (
    <div className="w-full flex justify-center items-center">
      <div className="pagination flex items-center gap-4">
        {renderButton(page - 1, <ArrowLeftIcon />, page === 1)}
        <div className="flex items-center">
          {currentPageArray.map((i) => (
            <button
              key={i}
              className={`flex items-center justify-center min-w-[32px] h-8 ${page === i ? "text-black dark:text-white font-semibold" : "text-gray-500"}`}
              onClick={() => onChange(i)}
            >
              {i}
            </button>
          ))}
        </div>
        {renderButton(page + 1, <ArrowRightIcon />, page === numPages || numPages === 0)}
      </div>
    </div>
  );
}



일단 되긴 하는데 자세히는 봐야할 듯