[React Query] Section2 - Pagination, Pre-fetching and Mutations(TIL)

이해용·2022년 8월 26일
0
post-thumbnail

※ 해당 섹션은 3버전 react query로 4버전과 다른 부분이 있을 수 있습니다.

Why don’t comments refresh?

  • Every query uses the same key (comments)
  • Data for queries with known keys only refetched upon trigger
  • Example trigger:
    • component remount
    • window refocus
    • running refetch funtion
    • automated refetch
    • query invalidation after a mutation

Solutions?

  • Options: remove programmatically for every new title
    • it’s not easy
    • it’s not really what we want
  • No reason to remove data from the cache
    • we’re not event performing the same query!
  • Query includes post ID
    • Cache on a per-query basis
    • don’t share cache for any “comments” query regardless of post id
  • What we really want: label the query for each post separately

Array as Query Key

  • Pass array for the query key, not just a string
  • Treat the query key as a dependency array
    • When key changes, create a new query
  • Query function values should be part of the key

ex) [”Comments”, post.id]

Pagination

  • Track current page in component state (currentPage)
  • Use query keys that include the page number [”posts”, currentPage]
  • User clicks “next page” or “previous page” button
    • update currenPage state
    • fire off new query
...
const maxPostPage = 10;

async function fetchPosts(pageNum) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${pageNum}`// 각 페이지에 맞는 값을 넣기 위해 pageNum 변수를 넣어준다.
  );
  return response.json();
}
...
const [currentPage, setCurrentPage] = useState(1); // 최초의 페이지를 1로 정한다.
...
const { data, isError, error, isLoading } = useQuery(
    ["posts", currentPage], // 현재 페이지에 맞는 currentPage를 의존성 배열로 사용하여 useQuery를 사용하며 구조분해할당을 진행한다.
    () => fetchPosts(currentPage), // 익명 함수를 사용하여 currentPage의 fetchPosts를 받는다.
    {
      staleTime: 2000,  // 만료시간 2초
    }
  );
...
		<button
          disabled={currentPage <= 1} // 현재 페이지가 1보다 작거나 같으면 비활성화
          onClick={() => {
            setCurrentPage((previousValue) => previousValue - 1);
          }} // 페이지가 1보다 작아지게 onClick event를 활성화한다.
        >
          Previous page
        </button>
        <span>Page {currentPage}</span> // 현재 페이지를 나타낸다.
        <button
          disabled={currentPage >= maxPostPage} // 현재 페이지가 maxPostPage 보다 크거나 같으면 비활성화
          onClick={() => {
            setCurrentPage((previousValue) => previousValue + 1); 
          }} // 페이지가 1보다 커지게 onClick event를 활성화한다.
        >
          Next page
        </button>

위처럼 페이지네이션을 진행하고 버튼을 누르면 데이터를 받아오기전에 loading 화면이 나타나게 된다.

이 부분을 방지하기 위해 Prefetching을 진행한다.

Prefetching

  • Prefetch
    • adds data to cache
    • automatically stale (configurable: 설정(변경) 가능한)
    • show while re-fetching
      • as long as cache hasn’t expired
  • Prefetching can be used for any anticipated data needs
    • not just pagination

리액트 쿼리를 실행하기 위해 useQueryClient 를 불러온다.

import { useQuery, useQueryClient } from "react-query";

...
const queryClient = useQueryClient();

useEffect(() => {
    if (currentPage < maxPostPage) { // 현재 페이지가 전체 페이지보단 적을 때
      const nextPage = currentPage + 1; // 다음 페이지는 현재 페이지 + 1 이고
      queryClient.prefetchQuery(["posts", nextPage], () => // 프리패치를 하기 위해 queryClient.prefetchquery를 기입 후 의존성 배열에 nextPage 기입
        fetchPosts(nextPage) // fetchPosts(nextPage)로 기입하여 다음의 데이터를 프리패칭한다.
      );
    }
  }, [currentPage, queryClient]); // useEffect의 의존성 배열은 currentPage 와 queryClient

const { data, isError, error, isLoading } = useQuery(
    ["posts", currentPage],
    () => fetchPosts(currentPage),
    {
      staleTime: 2000,
      keepPreviousData: true,
    }
  );
...
  • keepPreviousData: 새롭게 fetching 시 이전 데이터 유지 여부
    ⇒ true 로 작성하면 fetching 시 이전 데이터를 유지 시켜주므로 다음을 눌렀다가 이전으로 눌러도 데이터가 유지되기 때문에 loading 화면이 나타나지 않게 된다.

Mutations

  • Mutation: making a network call that changes data on the server (서버의 데이터를 변경하는 네트워크 호출하기)

useMutation

  • Similar to useQuery, but:
    • returns mutate function
    • doesn’t need query key
    • isLoading but no isFetching
    • by default, no retries (configurable!)
import { useQuery, useMutation } from "react-query"; // useMutation 선언
...
async function deletePost(postId) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/postId/${postId}`,
    { method: "DELETE" }
  );
  return response.json();
}
...
const deleteMutation = useMutation((postId) => deletePost(postId)); // useMutation에 deletePost 함수를 넣으나 useQuery와 달리 쿼리키가 없음
...
<button onClick={() => deleteMutation.mutate(post.id)}> // post.id가 deleteMutation.mutate에 각각의 값을 삭제할 수 있도록 설정
Delete</button>
...
      {deleteMutation.isError && (
        <p style={{ color: "red" }}>Error deleting the post</p>
      )} // deleteMutation에 에러가 발생되면 빨간색으로 <p>문구를 보여준다.
      {deleteMutation.isLoading && (
        <p style={{ color: "purple" }}>Deleting the post</p>
      )} // deleteMutation가 로딩중이면 보라색으로 <p>문구를 보여준다. 
      {deleteMutation.isSuccess && (
        <p style={{ color: "green" }}>Post has (not) been deleted</p>
      )} // deleteMutation가 완료되면 초록색으로 <p>문구를 보여준다.
...

update useMutation

delete와 같이 update도 거의 동일하게 진행하면 된다.

...
	const deleteMutation = useMutation((postId) => deletePost(postId));
  const updateMutation = useMutation((postId) => updatePost(postId));
...
			<button onClick={() => updateMutation.mutate(post.id)}>
        Update title
      </button>
...
			{updateMutation.isError && (
        <p style={{ color: "red" }}>Error updating the post</p>
      )}
      {updateMutation.isLoading && (
        <p style={{ color: "purple" }}>Updating the post</p>
      )}
      {updateMutation.isSuccess && (
        <p style={{ color: "green" }}>Post has (not) been updated</p>
      )}
...

Summary

  • Install package, create QueryClient and add QueryProvider
  • useQuery for data
    • return object also includes isLoading / isFetching and error
  • staleTime for whether or not to re-fetch (on trigger) (윈도우가 다시 포커스될 때 같은 특정 트리거에서 쿼리 데이터를 다시 가져올지를 결정한다. 다르게 말하면 staleTime 데이터가 사용가능한 상태로 유지되는 시간입니다. 서버로 돌아가 데이터가 여전히 정확한지 확인해야하는 시점까지요.)
  • cahceTime for how long to hold on to data after inactivity (데이터가 비활성화된 이후 남아있는 시간을 말합니다. 캐시된 데이터는 쿼리를 다시 실행했을 때 사용되죠. 데이터가 최신 상태인지 서버에서 확인하는 동안 자리표시자로 사용자에게 보여지게 됩니다.)
  • query keys as dependency arrays
  • pagination and pre-fetching
  • useMutation for server side-effects

reference
https://www.udemy.com/course/learn-react-query/
https://tanstack.com/query/v4/docs/guides/mutations
https://tanstack.com/query/v4/docs/reference/useMutation

profile
프론트엔드 개발자입니다.

3개의 댓글

comment-user-thumbnail
2023년 7월 20일

If the comments section were to constantly refresh in real-time for all users, it would generate a large number of frequent slither io requests to the server, which could lead to performance issues, increased server costs, and potential server crashes during high traffic periods.

답글 달기
comment-user-thumbnail
2023년 10월 29일

The roofing team I hired showcased an exceptional level of expertise. Their knowledge about various roofing materials and techniques was evident from our initial consultation. They guided me through the selection process https://www.theroofstore.net/

답글 달기
comment-user-thumbnail
2023년 11월 21일

I'm grateful for the unique lens through which you presented your drift hunters ideas in your article. It made for a memorable read

답글 달기