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

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

※ 해당 섹션은 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
프론트엔드 개발자입니다.
post-custom-banner

4개의 댓글

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

답글 달기
comment-user-thumbnail
2024년 9월 21일

Today I learned (TIL) about the relationship between pagination, pre-fetching, and mutations in modern web development, especially when it comes to performance optimization. Pagination helps in dividing large datasets into smaller, manageable chunks for easier navigation, while pre-fetching anticipates user needs by loading future pages or content in advance, improving the user experience. Mutations, on the other hand, refer to real-time updates that modify data in response to user actions. Understanding how these techniques work together can greatly enhance user interaction, even for niche industries like the roof repair business, where presenting accurate, updated data quickly is essential for engaging potential customers

답글 달기