[14-1] 페이지네이션
[14-2] state 끌어올리기
페이지 처리를 하는 방법에는 크게 일반적인 방식
과 무한스크롤 방식
, 2가지 방법이 있다.
페이지 번호를 클릭해서 이동하는 방식의 페이지 처리 방법이다.
게시판 형태의 페이지에서 가장 일반적으로 사용되는 방식이다.
📂 page 인자를 사용해서 게시글 목록 불러오기
가장 먼저 fetchBoards API를 활용해서 게시글 목록을 불러온다.
이 때, playground Docs를 참고하여 page 인자도 함께 불러와준다.
// gql query const FETCH_BOARDS = gql` query fetchBoards($page: Int) { fetchBoards(page: $page) { _id writer title } } `; // fetchBoards const { data } = useQuery(FETCH_BOARDS, { variables: { page: 1 } }); return ( <div> <h1>페이지네이션 연습 !!!</h1> {data?.fetchBoards?.map((el) => ( <div key={el._id}> {el.title} {el.writer} </div> ))} <span> 1 </span> <span> 2 </span> <span> 3 </span> </div> )
📂 페이지 클릭 시 게시글 목록 데이터 다시 불러오기 (refetch)
페이지네이션의 페이지 숫자를 클릭할 때마다. 목록에 뿌려진 데이터가 해당 페이지에 해당하는 데이터로 변경
graphQL의 useQuery에서 제공하는 refetch라는 함수를 활용하면 페이지 클릭 시 해당 페이지에 해당하는 데이터를 다시(re) 불러올(fetch) 수 있다.
refetch를 사용하기 위해서는 useQuery에서 data와 함께 refetch라는 함수를 불러와야 한다.
// gql query const FETCH_BOARDS = gql` query fetchBoards($page: Int) { fetchBoards(page: $page) { _id writer title } } `; // fetchBoards // data와 함께 refetch 가져오기 const { data, refetch } = useQuery(FETCH_BOARDS, { variables: { page: 1 } }); const onClickPage = (event) => { // 위에서 가져온 refetch 사용하기 refetch({ page: Number(event.target.id) }); }; return ( <div> <h1>페이지네이션 연습 !!!</h1> {data?.fetchBoards?.map((el) => ( <div key={el._id}> {el.title} {el.writer} </div> ))} <span onClick={onClickPage} id="1"> 1 </span> <span onClick={onClickPage} id="2"> 2 </span> <span onClick={onClickPage} id="3"> 3 </span> </div> )
📂 map을 이용해 페이지네이션 뿌리기
방금 한 것과 같이 모든 페이지네이션의 숫자를 직접 입력하는 작업은 비효율적이다.
배열과 map을 사용해 페이지네이션 만들기
// gql query const FETCH_BOARDS = gql` query fetchBoards($page: Int) { fetchBoards(page: $page) { _id writer title } } `; // fetchBoards // data와 함께 refetch 가져오기 const { data, refetch } = useQuery(FETCH_BOARDS, { variables: { page: 1 } }); const onClickPage = (event) => { // 위에서 가져온 refetch 사용하기 refetch({ page: Number(event.target.id) }); }; return ( <div> <h1>페이지네이션 연습 !!!</h1> {data?.fetchBoards?.map((el) => ( <div key={el._id}> {el.title} {el.writer} </div> ))} {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((el) => ( <span onClick={onClickPage} id={String(el)} key={el}> {` ${el} `} </span> ))} </div> )
📂 페이지네이션 next / prev 구현
export default function PaginationNextPage() { // 시작 페이지 const [startPage, setStartPage] = useState(1); const { data, refetch } = useQuery(FETCH_BOARDS, { variables: { page: 1 } }); const onClickPage = (event) => { refetch({ page: Number(event.target.id) }); console.log(event.target.id); }; // 이전 페이지 클릭 시 실행할 함수 const onClickPrevPage = () => { setStartPage((prev) => prev - 10); }; // 다음 페이지 클릭 시 실행할 함수 const onClickNextPage = () => { setStartPage((prev) => prev + 10); }; return ( <div> <h1>페이지네이션 연습 !!!</h1> {data?.fetchBoards?.map((el) => ( <div key={el._id}> {el.title} {el.writer} </div> ))} <span onClick={onClickPrevPage}>이전페이지 |</span> {new Array(10).fill(1).map((_, index) => ( <span onClick={onClickPage} id={String(index + startPage)} key={index + startPage} {` ${index + startPage} `} </span> ))} <span onClick={onClickNextPage}> 다음페이지</span> </div> ); }
📂 lastPage 설정
페이지네이션이 마지막 페이지를 훌쩍 지나갔는데도 계속해서 다음 페이지를 불러온다.
어떻게 하면 마지막 페이지를 설정해줄 수 있을까?
가장 먼저 DB에 등록된 게시글의 총 개수를 불러와서 마지막 페이지의 값을 구한다.
// fetchBoardsCount API 요청하기 const FETCH_BOARDS_COUNT = gql` query fetchBoardsCount { fetchBoardsCount } `; // lastPage 구하기 const lastPage = Math.ceil(dataBoardsCount?.fetchBoardsCount / 10);
const onClickPrevPage = () => { // startPage가 1이면 하단 스크립트를 실행하지 않고 종료한다. if (startPage === 1) return; setStartPage((prev) => prev - 10); }; const onClickNextPage = () => { // startPage + 10가 lastPage보다 클 경우 하단 스크립트를 실행하지 않고 종료한다. if (startPage + 10 > lastPage) return; setStartPage((prev) => prev + 10); };
{new Array(10).fill(1).map( (_, index) => index + startPage <= lastPage && ( <span onClick={onClickPage} id={String(index + startPage)} key={index + startPage} {` ${index + startPage} `} </span> ) )}
📂 이전 페이지/다음 페이지 이동 시 refetch
const onClickPrevPage = () => { // startPage가 1이면 하단 스크립트를 실행하지 않고 종료한다. if (startPage === 1) return; setStartPage((prev) => prev - 10); refetch({ page: startPage - 10 }); }; const onClickNextPage = () => { // startPage + 10가 lastPage보다 클 경우 하단 스크립트를 실행하지 않고 종료한다. if (startPage + 10 > lastPage) return; setStartPage((prev) => prev + 10); refetch({ page: startPage + 10 }); };
React의 데이터 흐름은 상위 컴포넌트에서 하위 컴포넌트로 전달하는 하향식, 단방향 데이터 흐름을 따르고 있다.
단방향 데이터 흐름의 장점
: 기능 변경 사항에 대한 코드 수정이 적어진다. 또한 복잡하지 않아 코드의 흐름을 알기 쉽다.
단방향 데이터 흐름의 단점
: 자식 컴포넌트1의 state를 자식 컴포넌트2에서 보여주는 것이 불가능 또한, 자식 컴포넌트2의 state를 부모 컴포넌트에서 보여주는 것도 불가능하다.
해결 방법
: 자식 컴포넌트의 state와 setState를 부모 컴포넌트로 끌어올려 선언해주면 된다. 그리고 props로 내려줄 경우, 자식 컴포넌트1, 2에서 모두 state를 사용할 수 있게 된다.페이지 번호를 클릭해서 이동하는 방식의 페이지 처리 방법이다.
단방향 데이터 흐름 해결 방법으로 state와 setState를 부모 컴포넌트로 끌어올려 선언해주면 된다.