ToyProject(더다주) Pagination & Sort

노영완·2023년 7월 21일
0

ToyProject(더다주)

목록 보기
4/13
post-thumbnail

시연영상

신제품을 번호에 따른 pagination을 구현 sort를 기준으로 상품이 나열되게끔 구현.

1. pagination bar number에 따른 데이터가 나오게끔.

2. 신상품 상품명 가격 낮은 높은 순으로 데이터가 나오게끔.

3. 위의 두가지를 유지한상태에서 같이 기능이 작동되게끔 가격 낮은순에서도 데이터가 나오면서 pagination이 나오게 끔.

첫 생각

서버 : pagination은 offset 방식을 사용 query는 page limit sort를 써 데이터가 나오게끔. sort의 기준은 조건문을 통해 각 조건에 맞는 데이터가 나오게끔 구현.
클라이언트 : useLocation을 이용 url에 쿼리부분을 가져와 데이터 fetch 실행 sort 부분은 select태그에 OnChage를 걸어 value값을 얻어와 url이 바뀌게끔 진행

Server code

productRouter.get("/new", async (req, res) => {
  try {
    const { page = 1, limit = 16, sort } = req.query;
    if (sort === "높은가격") {
      const [newProducts, count] = await Promise.all([
        await NewProduct.find({})
          .sort({ price: -1 })
          .limit(limit)
          .skip((page - 1) * 16),
        await NewProduct.count(),
      ]);
      return res.json({
        newProducts,
        count,
        totalPages: Math.ceil(count / limit),
      });
    }
    if (sort === "낮은가격") {
      const [newProducts, count] = await Promise.all([
        await NewProduct.find({})
          .sort({ price: 1 })
          .limit(limit)
          .skip((page - 1) * 16),
        await NewProduct.count(),
      ]);
      return res.json({
        newProducts,
        count,
        totalPages: Math.ceil(count / limit),
      });
    }
    if (sort === "상품명") {
      const [newProducts, count] = await Promise.all([
        await NewProduct.find({})
          .sort({ name: 1 })
          .limit(limit)
          .skip((page - 1) * 16),
        await NewProduct.count(),
      ]);
      return res.json({
        newProducts,
        count,
        totalPages: Math.ceil(count / limit),
      });
    }
    if (!sort || sort === "신상품") {
      const [newProducts, count] = await Promise.all([
        await NewProduct.find({})
          .sort({ _id: 1 })
          .limit(limit)
          .skip((page - 1) * 16),
        await NewProduct.count(),
      ]);
      return res.json({
        newProducts,
        count,
        totalPages: Math.ceil(count / limit),
      });
    }
  } catch (e) {
    return res.status(500).json({ err: e.message });
  }
});

page 값과 limit의 값은 1과 16으로 고정 sort의 값에 따른 데이터가 나오게끔 조건문을 써 데이터가 나오게끔 하고 find.sort({조건문에 맞게 기재})를 씀으로써 알맞는 데이터가 클라이언트 측에 전달되게끔 구현.
기존 조건문을 벗어나 위 상위 스코프에서 데이터를 return 해줄까 생각했다 이유는 sort에 값이 없을때는 기본적으로 신상품에 대한 데이터값이 나오게끔 구현하고 싶기 때문이었다. 하지만 클라이언트측에서 보내는 값은 null로 들어오였고 sort === "null"을 추가해 sort 값이 없을때는 신상품에 대한 값이 나오게 끔 구현.

Client code

const location = useLocation();
  const urlSearchParam = new URLSearchParams(location.search);
  const currentPageString = urlSearchParam.get("page");
  let currentPageNumber = Number(currentPageString);
const newProductAxios = useCallback(async () => {
    const data = await newProduct(location.search);
    return data;
  }, [location]);
  useEffect(() => {
    newProductAxios().then((data) => {
      setNewData(data.newProducts);
      setTotalData(data.count);
      setTotalPage(data.totalPages);
    });
  }, [newProductAxios]);
const queryNavigate = (pageNumber: number) => {
    const page = pageNumber;
    navigate(`/product?page=${page}&limit=16&sort=${querySort}`);
    window.scrollTo(0, 0);
  };
const onChangeSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setQuerySort(e.currentTarget.value);
    navigate(`/product?page=1&limit=16&sort=${e.currentTarget.value}`);
  };
const totalPageNumber = Array(totalPage)
    .fill(1)
    .map((n, idx) => n + idx);
return(
     <div>
        {totalPageNumber?.map((pageNumber, index) => {
          if (currentPageNumber === pageNumber) {
            return (
              <OnProductNumber
                onClick={() => queryNavigate(pageNumber)}
                key={index}

                {pageNumber}
              </OnProductNumber>
            );
          } else {
            return (
              <ProductNumber
                onClick={() => queryNavigate(pageNumber)}
                key={index}

                {pageNumber}
              </ProductNumber>
            );
          }
        })}
      </div>)

totalPageNumber에 따른 설명 링크
구현 목적은 페이지에 따른 pagination 과 select 값에 따른 sort 기능이다. 내가 req.query로 보내주어야 하는 값은 page, limit , sort의 값이다. 앞 선 세가지의 값을 담아 url로 보내고 나는 location을 이용해 url에 query 부분을 가져와 서버로 보내줄 것이다.

const queryNavigate = (pageNumber: number) => {
    const page = pageNumber;
    navigate(`/product?page=${page}&limit=16&sort=${querySort}`);
    window.scrollTo(0, 0);
  };
const onChangeSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setQuerySort(e.currentTarget.value);
    navigate(`/product?page=1&limit=16&sort=${e.currentTarget.value}`);
  };

queryNavigate 함수는 onClick으로 전달받은 pageNumber에 숫자를 page에 담고 onChangeSelect 함수를 통해 정의된 querySort state를 통해 sort에 값을 넣었고 url을 이동시켰다.
문제

navigate(`/product?page=1&limit=16&sort={querySort}`);

처음 내가 생각한 url 주소이다. 앞선 setQuerySort에서 state값은 변했기 때문에 리랜더링이 일어났고 변한 state값으로 url이 이동할 줄 알았는데 그러지 못했다. 이 부분은 그냥 currentTarget으로 바로 value값이 들어가게끔 해주었다.

pagination bar에 따른 number bar가 필요했다 구현 목적은 limit에 맞는 페이지 number 숫자 그리고 사용자가 알아보기 쉽게 페이지 number값이 active 되게끔.

1. 지금 나는 데이터의 갯수를 알고 이 프로젝트에서는 데이터의 양이 많지 않기 때문에 사실 내가 계산해 임의로 숫자배열을 만들어도 된다. 하지만 나중에 데이터의 양이 많아지고 혹은 limit이 바뀐다는 가정하에는 굉장히 번거로운 작업일 것이다. 나중에 유지보수에는 굉장히 안좋은 코드라고 판단 서버에서 전달받은 총 데이터의 갯수 / limit의 데이터를 받아 그거를 배열로 만들자.
2. active의 유무는 조건부 랜더링으로 핸들링 pageNumber와 비교가 필요한데 이거를 url에 page value값으로 비교하자

const urlSearchParam = new URLSearchParams(location.search);
const currentPageString = urlSearchParam.get("page");
let currentPageNumber = Number(currentPageString);
const totalPageNumber = Array(totalPage).fill(1).map((n, idx) => n + idx);
return(
     <div>
        {totalPageNumber?.map((pageNumber, index) => {
          if (currentPageNumber === pageNumber) {
            return (
              <OnProductNumber
                onClick={() => queryNavigate(pageNumber)}
                key={index}
                {pageNumber}
              </OnProductNumber>
            );
          } else {
            return (
              <ProductNumber
                onClick={() => queryNavigate(pageNumber)}
                key={index}
                {pageNumber}
              </ProductNumber>
            );
          }
        })}
      </div>)

일단 서버쪽에서 받은 총 page의 숫자를 배열로 만들자 const totalPageNumber로 만들었으며 위의 링크를 걸어놓았으니 확인하며 될 거 같다. 다음은 비교대상자다 나는 비교대상자를 url query에 있는 page에 값이 필요하다. new URLSearchParams(location.search)를 사용 get method를 사용해 page의 값을 가져왔다. 여기서 가져온 page의 값은 string이다. 나는 값과 타입까지 일치가 필요해서 Number()를 사용해 비교해 주었다.

2개의 댓글

comment-user-thumbnail
2023년 7월 21일

블로그 게시물을 잘 읽었습니다. 코드에 대한 설명이 자세해서 특히 도움이 되었어요. 클라이언트와 서버 사이의 데이터 통신 방식을 이해하는데 큰 도움이 되었습니다. 좋은 정보 공유 감사합니다!

1개의 답글