본문 바로가기
CodeIt_Sprint/React로 데이터 다루기

(7)React로 데이터 다루기_7_오프셋기반_페이지네이션 구현하기

by 코잼민 2025. 1. 2.

● 이론0_ 데이터의 offset , limit , hasNext 속성 정의

○ 데이터의 필드 구성 중 :

  • offset : 현재 fetch 된 데이터의 수
  • limit : 다음 fetch할 데이터의 수
  • hasNext :

- Api에 fetch할 데이터 수가 남아있다 => true

- Api에 fetch할 데이터수가 남아있지 않다 => false

● 이론1_ offset 기반 페이지네이션 구현 코드 작성하기

○ 순서1_ Get 메소드의 query에 offset , limit 반영하기

- Api.js

//매개인자를 객체로 수정
export async function getReviews({
  order = "createdAt",
  offset = 0,
  limit = 6,
}) {
  const query = `order=${order}&offset=${offset}&limit=${limit}`;

  const response = await fetch(
    `https://learn.codeit.kr/api/film-reviews?${query}`
  );

  const body = await response.json();
}

○ 순서2_ Get 메소드와 관련된 메소드 수정

- App.js

  • handLoad 메소드
  • useEffect 메소드

1_ handLoad()

  const loadReviews = async (options) => {
    const { reviews } = await getReviews(options);

    setItems(reviews);
  };

2_ useEffect()

const LIMIT = 6;//불러올 데이터의 숫자는 변하지 않는다.

  ...
  
  const loadReviews = async (options) => {
    const { reviews } = await getReviews(options);

    setItems(reviews);
  };
  
    //useEffect 부분
  useEffect(() => {
    loadReviews({ order, offset, limit: LIMIT });
    //상수형태로 limit는 고정
  }, [order]);

○ 순서3_ "더불러오기" 기능을 하면, 동작을 수행하면서, offset값은 변한다.

=> useState로 offset 값 등록

const [offset, setOffset] = useState(0);
//초기 offset값 0으로 초기화

○ 순서4_ "더불러오기" 버튼의 핸들러 등록

=> hadleLoad메소드 수정, handleMoreLoadClick 핸들러 구현

- handleLoad 수정

const handleLoad = async (options) => {
	//offset : fetch의 결과물
	const {reviews} = await getReviews(options);
    
    if(options.offset==0)
    {
		setItems(reviews);	
    }
    else
    {
		//spread 문법으로, 기존 items의 베열에 review를 추가적용
        setItems([...items,...reviews]);
	}
    
    //offset과 hasNext 업데이트 부분
    setOffset(items.length);
}

- handleMoreLoadClick 핸들러 

const handleMoreLoad = () => {
	handLoad({order, offset , limit : LIMIT});
}

○ 순서5_ 버튼 태그 추가 + 핸들러 적용

  return (
    <div>
      <div>
        <button onClick={handleNewestClick}>최신순</button>
        <button onClick={handleBestClick}>베스트순</button>
      </div>
      <ReviewList items={sortedItems} onDelete={handleDelete} />
      <button disabled={!hasNext} onClick={handleLoadMore}>
        더 보기
      </button>
    </div>
  );

 

=> 여기까지 결과물 :

- App.js :

import { useState, useEffect } from "react"; //useEffect 추가하기기
import ReviewList from "./ReviewList";
import { getReviews } from "../api";
const LIMIT = 6;

function App() {
  const [items, setItems] = useState([]);
  const [order, setOrder] = useState("createdAt");
  const [offset, setOffset] = useState(0);
  //Get 메소드 있는 부분 모두 수정 :

  //loadReviews
  const loadReviews = async (options) => {
    const { reviews } = await getReviews(options);

    if (options.offset === 0) {
      setItems(reviews);
    } else {
      setItems([...items, ...reviews]);
    }

    setOffset(options.offset + items.length);
  };

  //useEffect 부분
  useEffect(() => {
    loadReviews({ order, offset, limit: LIMIT });
  }, [order]);

  const sortReviews = items.sort((a, b) => {
    return b[order] - a[order];
  });

  const handleRatingSort = () => {
    setOrder("rating");
  };

  const handleCreatedAtSort = () => {
    setOrder("createdAt");
  };

  const handleMoreLoad = async () => {
    await loadReviews({ order, offset, limit: LIMIT });
  };
  const handleDeleteReview = (id) => {
    const deleteAfterReviews = items.filter((item) => id !== item.id);

    setItems(deleteAfterReviews);
  };

  return (
    <div>
      <div>
        <button onClick={handleCreatedAtSort}>최신순</button>
        <button onClick={handleRatingSort}>베스트순</button>
      </div>
      <ReviewList items={sortReviews} onDelete={handleDeleteReview} />
      <div>
        <button onClick={handleMoreLoad}>더보기</button>
      </div>
    </div>
  );
}

export default App;

- api.js

//매개인자에 order 추가
export async function getReviews({
  order = "createdAt",
  offset = 0,
  limit = 6,
}) {
  const query = `order=${order}&offset=${offset}&limit=${limit}`;
  //Get메소드에 query문자열 따로 추가

  const response = await fetch(
    `https://learn.codeit.kr/api/film-reviews?${query}`
  );
  //fetch의 url에 query작용
  const body = await response.json();
  return body;
}

 

○ 순서6_ hasNext를 이용해, 데이터를 모두 불러왔을 시(hasNext==false), 버튼 비활성화 시키기

①. hasNext를 useState 등록

②. handleLoad에서,  fetch의 반환값에 따로 paging을 추가, setHasNext에 paging의 hasNext를 업데이트

③. button 태그의 disable 속성을 이용해 , hasNext가 false일 시, 비활성화 구현

const [hasNext , setHasNext] = useState(false);

const handleLoad = async (options)=>{
	
    const {reviews , paging} = await getReviews(options);
    //paging 객체의 프로퍼티 추가
    if(options.offset===0)
    {
		setItems(reviews);
	}
    else
	{
		setItems([...items, ...options]);
	}
    
    setOffset(items.length);
    
    setHasNext(paging.hasNext);
    //paging의 hasNext로 업데이트
    
}

<button disable={!hasNext} onClick={handleMoreLoad}>더 보기</button>

 

=> 여기까지 결과물 :

import { useState, useEffect } from "react"; //useEffect 추가하기기
import ReviewList from "./ReviewList";
import { getReviews } from "../api";
const LIMIT = 6;

function App() {
  const [items, setItems] = useState([]);
  const [order, setOrder] = useState("createdAt");
  const [offset, setOffset] = useState(0);
  const [hasNext, setHasNext] = useState(false);

  const loadReviews = async (options) => {
    const { reviews, paging } = await getReviews(options);

    if (options.offset === 0) {
      setItems(reviews);
    } else {
      setItems([...items, ...reviews]);
    }

    setOffset(options.offset + items.length);
    setHasNext(paging.hasNext);
  };

  //useEffect 부분
  useEffect(() => {
    loadReviews({ order, offset, limit: LIMIT });
  }, [order]);

  const sortReviews = items.sort((a, b) => {
    return b[order] - a[order];
  });

  const handleRatingSort = () => {
    setOrder("rating");
  };

  const handleCreatedAtSort = () => {
    setOrder("createdAt");
  };

  const handleMoreLoad = async () => {
    await loadReviews({ order, offset, limit: LIMIT });
  };
  const handleDeleteReview = (id) => {
    const deleteAfterReviews = items.filter((item) => id !== item.id);

    setItems(deleteAfterReviews);
  };

  return (
    <div>
      <div>
        <button onClick={handleCreatedAtSort}>최신순</button>
        <button onClick={handleRatingSort}>베스트순</button>
      </div>
      <ReviewList items={sortReviews} onDelete={handleDeleteReview} />
      <div>
        <button disabled={!hasNext} onClick={handleMoreLoad}>
          더보기
        </button>
      </div>
    </div>
  );
}

export default App;