에러해결기

Pagination 각 페이지마다 나오는 목록의 개수가 다를때 (조건이 일치하지 않아도 동일한 길이를 유지하는 map함수)

hihiha2 2023. 4. 24. 13:30

 

google books API를 통해서 받아온 데이터를 페이지네이션 기능을 넣으려고 했는데,

생각지 못한 오류가 발생했다.

 

에러는 아니어서 에러메시지가 뜨지는 않았지만 내가 생각한대로 나오지 않고,

페이지마다 나오는 목록의 개수가 제멋대로였다 😓

 

첫페이지의 목록의 개수가 3개인데, 다음은 5개, 다음은 0개 이런식...

 

그래서 limit의 값을 고정값으로 5, 10이렇게 바꿔도

최대값이 그 값이 되긴 하지만 고정적인 개수로는 나오지 않았다.

 

 

페이지네이션 오류

 

 

 

🔍 원인

원인은 return앞에 적어준 if문 때문이었다.

🔫 나의의도: 받아오는 데이터중 빈데이터가 있는 것은 건너뛰고, 데이터에 값이 모두 담긴 것만 받아오고 싶다.

🔫 의도와는 다른 결과값: 데이터가 없는 곳은 빈값을 넣는다 ➡️ 목록에서 한자리를 차지함

 

이러한 오류는 map함수의 특징때문에 발생하는데 자세하게 적으면 아래와 같다.

 

🕵️‍♀️ 오류가 발생한 원인은 map함수!

map()함수는 배열의 각 요소에 대해 콜백함수를 호출하고 각 요소를 변환한 새로운 배열을 반환한다. 콜백함수에서는 요소를 변환하는 코드를 작성한다.

하지만, 콜백함수에서 조건문을 사용하여 return하지 않는 경우, 해당 조건에 맞지 않는 요소undefined를 반환한다. 이렇게 반환된 요소는 배열에 포함되지 않지만, map()함수는 요소의 개수와 배열의 길이를 동일하게 유지한다.

 

예시>

const arr = [1, 2, 3, 4, 5];

const result = arr.map((num) => { if (num % 2 === 0) { return num ** 2; } });

console.log(result);

// [undefined, 4, undefined, 16, undefined]

 

1,2,3,4,5의 값을 담은 배열을 map을 통해서 만약 짝수라면 제곱을 해준다고 해보자.

이럴경우 짝수가 아닌 1,3,5가 없는,

2의 제곱인 4, 4의 제곱인 16이 담긴 배열인 [4,16]이 결과값으로 나온다고 생각했지만

[undefined, 4, undefined, 16, undefined]이 결과값으로 나온다.

 

 

➡️ 이러한 원인때문에 각 페이지별로 나오는 목록의 개수가 달랐던 것이었다..!!

 

반환되는 값이 undefined일 수 있으므로 이에 대한 예외처리가 필요하다.

if문의 조건에 맞지 않은 경우 null이나 빈배열을 반환하도록 변경하면 된다.

 

 

❌ 이전코드 ❌

 <main>
        <div className="container">
          {bookData?.slice(offset, offset + limit).map((books) => {
            if (
              books.volumeInfo?.imageLinks?.smallThumbnail &&
              books.saleInfo?.listPrice?.amount &&
              books.volumeInfo?.title
            ) {
              return (
                <BookCard books={books} setSelectedBook={setSelectedBook} />
              );
            }
          })}
          {selectedBook && (
            <BookDetailModal
              selectedBook={selectedBook}
              onClose={handleCloseBookDetailModal}
            />
          )}
        </div>
      </main>

이전코드들 보면 if문을 map뒤, return앞에 적었다.

 

 

✅ 해결 

if문 대신 filter를 사용해서 거르고 싶은 조건을 거른다.

조건을 map안에 적어주는 것이 아니라, filter를 통해 값을 거른다음 map을 돌린다.

const formattedData = bookData
?.filter(
(item) =>
item.volumeInfo?.imageLinks?.smallThumbnail &&
item.volumeInfo?.title &&
item.saleInfo?.listPrice?.amount
)
.slice(offset, offset + limit);
<main>
<div className="container">
{formattedData.map((books) => {
return (
<BookCard
key={books.id}
books={books}
setSelectedBook={setSelectedBook}
/>
);
})}
{selectedBook && (
<BookDetailModal
selectedBook={selectedBook}
onClose={handleCloseBookDetailModal}
/>
)}
</div>
</main>

 

😊 limit=8로 해주고 페이지를 보면 이제 정상적으로 나오는것을 볼 수 있다!!

 

 

 

 

💻 전체코드

import { useState, useEffect } from "react";
import { getBookData } from "../../API/Api.js";
import BookCard from "../BookCard/BookCard.js";
import Pagination from "../Pagination/Pagination.js";
import "./Posts.css";

export default function Posts({
  bookData,
  selectedBook,
  setSelectedBook,
  BookDetailModal,
  handleCloseBookDetailModal,
}) {
  const [posts, setPosts] = useState([]);
  const limit = 8;
  const [page, setPage] = useState(1);
  const offset = (page - 1) * limit;

  const formattedData = bookData
    ?.filter(
      (item) =>
        item.volumeInfo?.imageLinks?.smallThumbnail &&
        item.volumeInfo?.title &&
        item.saleInfo?.listPrice?.amount
    )
    .slice(offset, offset + limit);

  const getApi = async () => {
    try {
      const response = await getBookData();
      const { data } = response;

      setPosts(data.items);
    } catch (e) {
      console.error(e);
    }
  };


  useEffect(() => {
    getApi();
  }, []);

  return (
    <div className="content-box">
      <header>
        <h1>게시물 목록</h1>
      </header>
      <main>
        <div className="container">
          {formattedData.map((books) => {
            return (
              <BookCard
                key={books.id}
                books={books}
                setSelectedBook={setSelectedBook}
              />
            );
          })}
          {selectedBook && (
            <BookDetailModal
              selectedBook={selectedBook}
              onClose={handleCloseBookDetailModal}
            />
          )}
        </div>
      </main>
      <footer>
        <Pagination
          total={posts.length}
          limit={limit}
          page={page}
          setPage={setPage}
        />
      </footer>
    </div>
  );
}