useInfiniteQuery - 무한 스크롤리액트 심화/react-query2022. 6. 19. 07:45
Table of Contents
Why infinte-query?
- react-query의 캐싱 기능이 없이 무한 스크롤을 구현했다고 생각해보자!
- useState에 서버에서 받아온 데이터를 쌓아나갈 때, 데이터에 좋아요 버튼이 있어서 클릭(좋아요 API 호출)한다고 하면 화면에 바로 반영이 되지 않을 것이다. 새로고침을 해서 API를 차례로 호출하면서 다시 좋아요가 눌린 요소까지 내려가야 좋아요가 눌린 상태를 확인할 수 있다! 좋아요가 눌린 최신 서버 데이터가 아니기 때문이다!
- 하지만 react-query는 useMutate로 서버의 데이터를 변경하는 API를 호출(좋아요 버튼 클릭)해서 캐싱 데이터를 초기화해서 다시 서버에서 최신 데이터를 받아올 수 있다!
→ 좋아요가 눌린 서버의 데이터를 바로 받아와서 즉시 좋아요가 눌렸다는 표시를 해줄 수 있음! - 즉, infinite-query를 사용하면 1에서 5페이지까지 데이터를 받아왔다고 가정했을 때, 만약 2페이지의 내용을 useMutate로 서버의 데이터를 변경한 후 캐싱 데이터를 초기화하면 새로고침을 안해도 2페이지의 내용이 변경된 최신의 1~5페이지 까지의 데이터를 즉시 서버에서 받아와서 화면에 바로 보여줄 수 있다
- 아래처럼 5번 API 호출해서 데이터가 쌓여있을 때, 페이지의 일부가 변경되면 여태까지 호출한 API를 알아서 호출해서 최신의 데이터를 유지한다!
await api.get("/api/board?page=&size=4")
await api.get("/api/board?page=1&size=4")
await api.get("/api/board?page=2&size=4")
await api.get("/api/board?page=3&size=4")
await api.get("/api/board?page=4&size=4")
- 무한 스크롤을 구현해서 쌓인 데이터 값이 변화할 때 자동으로 서버의 최신데이터를 불러오는 기능이 필요하지 않다면 굳이 infinite query를 사용하지 않아도 된다!
infinite scroll 구현해보기
useInifinteQuery 만들기
- 아래는 customHooks로 useInfiniteQuery를 사용한 예시이다!
페이지 조회 함수 만들기
- 페이지 조회 함수에 파라미터로 맨 처음 페이지 값(디폴트 값, 안넣으면 undefined)을 넣어주고 페이징 API를 호출한 후 리턴 값으로 board_page에 화면에 보여줄 페이지의 데이터를 넣어주고 current_page에 현재 페이지를 넣어주고 isLast에 서버에서 해당 페이지가 마지막인지 여부를 알려주는 true/false 값을 넣어준다
- 리턴하는 객체의 key 값은 자유임
infinite query 인스턴스 만들기
- useInfiniteQuery의 첫번째 파라미터로 query_key를 넣어주고 두번째 파라미터로 페이지 조회 함수 넣어준다.
- getNextPageParam은 다음 pageParam을 가져오는 부분인데 lastPage는 바로 직전에 호출한 페이지 조회 함수의 리턴값이다
- 페이지 조회 함수의 리턴값으로 현재 페이지를 넣어준 이유는 바로 다음 페이지의 번호를 가져오기 위함이였다!!
- 커스텀 훅의 리턴 값을 구조분해 할당으로 data, fetchNextPage, isSuccess, hasNextPage를 담았는데 data는 queryInstance의 데이터 정보를 포함하고 fetchNextPage는 다음 페이지를 가져오는 기능이고 isSuccess는 데이터를 가져왔는지 알려주는 true/false 여부이다. hasNextPage는 다음 페이지가 있는지 알려주는 true/false 여부이다.
- 컴포넌트에서 fetchNextPage를 호출하면 getNextPageParam에서 리턴한 다음 페이지 번호를 페이지 조회 함수의 pageParam의 값으로 변경시킨후 페이지 조회 함수의 API를 호출해서 다음 페이지의 데이터를 가져온다!!
- 또한 hasNextPage는 getNextPageParam에서 마지막으로 리턴한 값을 보고 유효한 값이 아니면(undefined)면 false를 리턴한다! 이를 사용하기 위해 페이지 조회 함수에서 리턴값에 마지막 페이지인지 알여주는 isLast를 넘겨줬으며 만약 isLast가 true가 되면 getNextPageParam은 undefined를 리턴하고 hasNextPage는 false가 된다!!
import { useInfiniteQuery } from "react-query";
import api from "../utils/api";
export const useInfiniteScrollQuery = () => {
const getPageBoard = async ({ pageParam = 0 }) => {
const res = await api.get(
`api/board?page=${pageParam}&size=4`
);
return {
// 실제 데이터
board_page: res.data.content,
// 반환 값에 현재 페이지를 넘겨주자
current_page: pageParam,
// 페이지가 마지막인지 알려주는 서버에서 넘겨준 true/false 값
isLast: res.data.last,
};
};
const {
data: getBoard,
fetchNextPage: getNextPage,
isSuccess: getBoardIsSuccess,
hasNextPage: getNextPageIsPossible,
} = useInfiniteQuery(["page_board_list"], getPageBoard, {
getNextPageParam: (lastPage, pages) => {
// lastPage와 pages는 콜백함수에서 리턴한 값을 의미한다!!
// lastPage: 직전에 반환된 리턴값, pages: 여태 받아온 전체 페이지
if (!lastPage.isLast) return lastPage.current_page + 1;
// 마지막 페이지면 undefined가 리턴되어서 hasNextPage는 false가 됨!
return undefined;
},
});
return { getBoard, getNextPage, getBoardIsSuccess, getNextPageIsPossible };
};
- useInfiniteQuery를 이용해 호출되는 데이터들은 useQuery와 마찬가지로 data로 접근할 수 있고 페이지별로 pages라는 배열의 요소에 담기게 된다
- 구조분해 할당으로 getBoard라는 이름으로 넘겨준 useInfiniteQuery의 data를 출력해보면 아래처럼 나온다!
컴포넌트에서 infinite scroll 기능 적용하기
- 우선 useInfiniteQuery를 작성한 커스텀 훅을 import하고 useInfiniteQuery의 data와 fetchNextPage와 isSuccess, hasNextPage를 사용하자
- useInView를 이용해서 마지막 요소가 보이면 다음 페이지를 가져오게 구현하면 끝이다!
- 마지막 페이지에서 서버에서 넘겨준 isLast가 false라면 hasNextPage가 false가 되어서 마지막 요소가 보여도 더 이상 다음 페이지를 가져오지 않는다!
- 만약 페이지를 불러왔는데 한 화면에 모든 요소가 다보여서 isView가 false로 안변하고 계속 true일 수 있으니 getBoard도 useEffect의 의존성 배열에 넣어주자!
import { useInfiniteScrollQuery } from "../../react-query/useInfiniteScrollQuery";
import { useInView } from "react-intersection-observer";
const Home = () => {
// 무한스크롤 사용: infiniteQuery, intersection-observer
// getBoard: data, getNextPage: fetchNextPage, getBoardIsSuccess: isSuccess
const { getBoard, getNextPage, getBoardIsSuccess, getNextPageIsPossible } = useInfiniteScrollQuery();
const [ref, isView] = useInView();
useEffect(() => {
// 맨 마지막 요소를 보고있고 더이상 페이지가 존재하면
// 다음 페이지 데이터를 가져옴
if (isView && getNextPageIsPossible) {
getNextPage();
}
}, [isView, getBoard]);
return (
<div className="home-wrapper">
{
// 데이터를 불러오는데 성공하고 데이터가 0개가 아닐 때 렌더링
getBoardIsSuccess && getBoard.pages
? getBoard.pages.map((page_data, page_num) => {
const board_page = page_data.board_page;
return board_page.map((item, idx) => {
if (
// 마지막 요소에 ref 달아주기
getBoard.pages.length - 1 === page_num &&
board_page.length - 1 === idx
) {
return (
// 마지막 요소에 ref 넣기 위해 div로 감싸기
<div ref={ref} key={item.board_id}>
<Card
나머지 props
/>
</div>
);
} else {
return (
<Card
key={item.board_id}
나머지 props
/>
);
}
});
})
: null
}
</div>
);
};
export default Home;
'리액트 심화 > react-query' 카테고리의 다른 글
react-query 모듈화 - Custom Hooks (0) | 2022.06.18 |
---|---|
react-query (0) | 2022.06.13 |
@덕구공 :: Duck9s'
주니어 개발자에욤
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!