React query - pagination, prefetching, mutations

zooyahoยท2022๋…„ 10์›” 6์ผ
0
post-thumbnail
post-custom-banner

๋ชฉ์ฐจ

  • fetching data
  • loading/ error states
  • dev tools
  • pagination
  • prefetching
  • mutations

๐Ÿ”— Get start

1. ์„ค์น˜

npm i react-query@^3

2. query client

  • ์ฟผ๋ฆฌ์™€ ์„œ๋ฒ„์˜ ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ

3. queryProvider

  • ์ž๋…€ ์ปดํฌ๋„ŒํŠธ์— ์บ์‹œ์™€ ํด๋ผ์ด์–ธํŠธ ๊ตฌ์„ฑ์„ ์ œ๊ณตํ•  queryProvider ์ ์šฉ
improt { QueryClientProvider, QueryClient } from 'react-query';

4. ReactQueryDevtools

improt { ReactQueryDevtools } from 'react-query/devtools';

5. useQuery

  • ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด
  • ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ 3ํšŒ ์žฌ์‹œ๋„ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•œ๋‹ค.

โญ๏ธ isLoading vs isFetching

  • isFetching: ๋น„๋™๊ธฐ ์ฟผ๋ฆฌ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์•˜์Œ์„ ์˜๋ฏธ(ํŽ˜์นญ์„ ์™„๋ฃŒํ•˜์ง€ ์•Š์•˜๋‹ค๋Š” ์˜๋ฏธ)
  • isLoading: isFetching์˜ ํ•˜์œ„ ์ง‘ํ•ฉ, ๊ฐ€์ ธ์˜ค๋Š” ์ƒํƒœ์— ์˜๋ฏธ๊ฐ€ ์žˆ์Œ. ๋น„๋™๊ธฐ ์ฟผ๋ฆฌ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š๊ณ  ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ ๋˜ํ•œ ์—†๋Š” ๊ฒƒ. ์ฐธ์ด๋ฉด isFetching ๋˜ํ•œ ์ฐธ์ด๋‹ค!

๐Ÿ“Ž stale time vs cache time

  • stale data: ๋งŒ๋ฃŒ๋œ ๋ฐ์ดํ„ฐ,
  • ๋ฐ์ดํ„ฐ ๋ฆฌํŽ˜์นญ์€ ๋งŒ๋ฃŒ๋œ ๋ฐ์ดํ„ฐ์—์„œ๋งŒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  • stale time: ๋ฐ์ดํ„ฐ๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ์ตœ๋Œ€ ๋‚˜์ด, ๊ธฐ๋ณธ๊ฐ’ 0
  • cache๋Š” ๋‚˜์ค‘์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ
  • ํŠน์ • ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ํ™œ์„ฑ useQuery๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋Š” cold storage๋กœ ์ด๋™, ๊ตฌ์„ฑ๋œ cache time์ด ์ง€๋‚˜๋ฉด ์บ์‹œ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŒ๋ฃŒ๋˜๋ฉฐ ์œ ํšจ์‹œ๊ฐ„์˜ ๊ธฐ๋ณธ๊ฐ’์€ 5๋ถ„(cache time์˜ ๊ธฐ๋ณธ๊ฐ’)์ด๋‹ค.
  • cache time์€ ํŠน์ • ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ useQuery๊ฐ€ ํ™œ์„ฑํ™”๋œ ํ›„ ๊ฒฝ๊ณผํ•œ ์‹œ๊ฐ„, ํŽ˜์ด์ง€์— ํ‘œ์‹œ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํŠน์ • ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด useQuery๋ฅผ ์‚ฌ์šฉํ•œ ์‹œ๊ฐ„, ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„ํ™œ์„ฑํ™” ๋œ ์ดํ›„ ๋‚จ์•„ ์žˆ๋Š” ์‹œ๊ฐ„

๐Ÿ”— pagination , prefetching, mutaion

  • ๊ฒŒ์‹œ๊ธ€ ํด๋ฆญ ์‹œ ๋Œ“๊ธ€์ด fetching์ด ์ผ์–ด๋‚˜์ง€ ์•Š์Œ
    ๐Ÿ‘‰๐Ÿป ํ•ด๊ฒฐ) ์ƒˆ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€ ์ œ๋ชฉ์„ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฅผ ๋ฌดํšจํ™” ์‹œ์ผœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๊ฒŒ ํ•œ๋‹ค.
    ๐Ÿ‘‰๐Ÿป ๋ฌธ์ œ) ์œ„์˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ๊ฒŒ์‹œ๊ธ€2๋ฅผ ํด๋ฆญํ•˜๋ฉด ๊ฒŒ์‹œ๊ธ€1์˜ comments data๊ฐ€ ์บ์‹œ์—์„œ ์‚ญ์ œ๊ฐ€ ๋˜๊ธฐ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค(๊ฐ™์€ ์บ์‹œ๊ณต๊ฐ„์„ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค)
    ๐Ÿ‘‰๐Ÿป ์ฐ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•) ๊ฐ ๊ฒŒ์‹œ๋ฌผ์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ์— ๋ผ๋ฒจ์„ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค. -> ์ฟผ๋ฆฌํ‚ค์— ๋ฌธ์ž์—ด ๋Œ€์‹  ๋ฐฐ์—ด์„ ์ „๋‹ฌ ex) ['comments', postId]

โ— pagination

  • ํ˜„์žฌ ํŽ˜์ด์ง€๋ฅผ ํŒŒ์•…ํ•ด์คŒ.
  • ํŽ˜์ด์ง€๋งˆ๋‹ค ์ฟผ๋ฆฌํ‚ค๊ฐ€ ํ•„์š”ํ•จ์œผ๋กœ ์ฟผ๋ฆฌํ‚ค๋ฅผ ๋ฐฐ์—ด๋กœ ์—…๋ฐ์ดํŠธํ•ด์„œ fetchingํ•œ๋‹ค.
  • ์•„๋ž˜ ์ฝ”๋“œ๋Š” nextํŽ˜์ด๋น„ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋กœ๋”ฉ ์ธ๋””์ผ€์ด์…˜์ด ๋ฐœ์ƒํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋ฐฉํ•ดํ•จ -> ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋™์•ˆ ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ฒŒ prefetching์„ ์‚ฌ์šฉํ•œ๋‹ค!!
const [currentPage, setCurrentPage] = useState(1); // ํ˜„์žฌ ํŽ˜์ด์ง€

// data์— ๋‹ค์Œ ํŽ˜์ด์ง€ ์œ ๋ฌด์— ๋Œ€ํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ๋ฒ„ํŠผ์„ ๋น„.ํ™œ์„ฑํ™”ํ• ์ง€ ํŒ๋‹จํ•˜๋Š” ์ง€ํ‘œ๋กœ ์‚ฌ์šฉํ•œ๋‹ค
const { data, isLoading, isError, error } = useQuery(
    ["posts", currentPage],
    () => fetchPosts(currentPage),
    {
      staleTime: 2000,
      keepPreviousData: true, // ์ด์ „ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ”์„ ๋•Œ ์บ์‹œ์— ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค
    }
  );

...


return (
  ...
   <div className="pages">
     <button
       disabled={currentPage <= 1}
       onClick={() => {
         setCurrentPage((prevValue) => prevValue - 1);
       }}
   	 >
      Previous page
     </button>
     <span>Page {currentPage}</span>
     <button
        disabled={currentPage >= maxPostPage}
        onClick={() => {
          setCurrentPage((prevValue) => prevValue + 1);
        }}
     >
      Next page
     </button>
   </div>
);

โ— prefetching

  • ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์™€ ์บ์‹œ์— ๋„ฃ์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋‹ค๋ฆด ํ•„์š” ์—†๊ฒŒ ํ•œ๋‹ค.
  • ๋ฐ์ดํ„ฐ์˜ ๊ธฐ๋ณธ๊ฐ’์€ stale(๋งŒ๋ฃŒ)์ƒํƒœ์ด๋‹ค!!
  • ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋งŒ๋ฃŒ์ƒํƒœ์—์„œ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง์ž„.
  • ์˜ˆ๋ฅผ ๋“ค์–ด ํƒญ๊ณผ ๊ฐ™์ด ํ†ต๊ณ„์ ์œผ๋กœ ํŠน์ • ํƒญ์„ ๋ˆ„๋ฅผ ํ™•๋ฅ ์ด ๋†’์„ ๊ฒฝ์šฐ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ์ข‹์Œ!!
import { useQuery, useQueryClient } from "react-query";

const maxPostPage = 10;

const queryClient = useQueryClient();
  useEffect(() => {
    if (currentPage < maxPostPage) {
      // ํŽ˜์ด์ง€๊ฐ€ 11์ผ ๋•Œ fetchํ•˜์ง€ ์•Š๊ฒŒ
      const nextPage = currentPage + 1;
      queryClient.prefetchQuery(["posts", currentPage], () =>
        fetchPosts(nextPage)
      );
    }
  }, [currentPage, queryClient]);

๐Ÿ“Œ prefetchQuery(["posts", currentPage], ...)
๐Ÿ’ฅ currentPage -> ์˜์กด์„ฑ ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•˜๋Š” ์ด์œ 

  • ์ฟผ๋ฆฌ ๋ฐ์ดํ„ฐ๋Š” ๋งŒ๋ฃŒ(stale) ์ƒํƒœ์ด์ง€๋งŒ ๋ฆฌํŽ˜์น˜(refetch)๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ๋Œ€์ƒ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค!
    ๐Ÿ’ฅ "posts"
  • ๋ฐ์ดํ„ฐ ๋ฌดํšจํ™”๋ฅผ ์‹œ์ž‘ํ• ๋•Œ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฅผ ๋ฌดํšจํ™” ํ•˜๋ ค๋ฉด ๋ชจ๋“  ๋ฐฐ์—ด์— ๊ณตํ†ต์ ์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • ํŠนํžˆ ๊ณตํ†ต ์ ‘๋‘์‚ฌ๊ฐ€ ์žˆ์œผ๋ฉด ํ•œ๋ฒˆ์— ๋ชจ๋‘ ๋ฌดํšจํ™” ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค!!

โœ… useQuery(, , {keepPreviousData: true})
keepPreviousData: true, // ์ด์ „ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ”์„ ๋•Œ ์บ์‹œ์— ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค. ์ฟผ๋ฆฌ ํ‚ค๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๊นŒ์ง€ ์ด์ „์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋œ๋‹ค. ํŽ˜์น˜ํ•˜๋Š” ๋™์•ˆ ์ด์ „ ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋ฆฌํ‘œ์‹œ์ž๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ

โญ๏ธ isLoading vs isFetching

export function Posts() {
  const [currentPage, setCurrentPage] = useState(1); // ํ˜„์žฌ ํŽ˜์ด์ง€
  const [selectedPost, setSelectedPost] = useState(null);

  const queryClient = useQueryClient();
  useEffect(() => {
    if (currentPage < maxPostPage) {
      // ํŽ˜์ด์ง€๊ฐ€ 11์ผ ๋•Œ fetchํ•˜์ง€ ์•Š๊ฒŒ
      const nextPage = currentPage + 1;
      queryClient.prefetchQuery(["posts", currentPage], () =>
        fetchPosts(nextPage)
      );
    }
  }, [currentPage, queryClient]);

  // data์— ๋‹ค์Œ ํŽ˜์ด์ง€ ์œ ๋ฌด์— ๋Œ€ํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ๋ฒ„ํŠผ์„ ๋น„.ํ™œ์„ฑํ™”ํ• ์ง€ ํŒ๋‹จํ•˜๋Š” ์ง€ํ‘œ๋กœ ์‚ฌ์šฉํ•œ๋‹ค
  const { data, isLoading, isError, error } = useQuery(
    ["posts", currentPage],
    () => fetchPosts(currentPage),
    {
      staleTime: 2000,
      keepPreviousData: true, // ์ด์ „ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ”์„ ๋•Œ ์บ์‹œ์— ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค
    }
  ); // key / ๋น„๋™๊ธฐ ํ•จ์ˆ˜
  // fetchPosts๊ฐ€ ๋น„๋™๊ธฐ์ด๊ธฐ ๋•Œ๋ฌธ์— ์™„๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” query์— ์ €์žฅ๋˜์ง€ ์•Š์Œ
  if (isLoading) return <div>Loading....</div>;
  if (isfetching) return <div>Fetching in pregress</div>;
  if (isError) return <div>{error.toString()}</div>;

  return (...);
}
  • nextํŽ˜์ด์ง€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๊ฒฝ์šฐ isFetching์˜ ๊ฒฝ์šฐ ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋”๋ผ๋„ ์ถœ๋ ฅ๋จ
  • prefetchํ•œ ๊ฒฝ์šฐ ์บ์‹œ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— isLoading์€ ์ถœ๋ ฅ์ด ์•ˆ๋˜์ง€๋งŒ isfetching์€ ์บ์‹œ๋ฐ์ดํ„ฐ ์œ ๋ฌด์™€ ์ƒ๊ด€์—†์ด ํŽ˜์น˜์ผ๋•Œ(์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘์ผ๊ฒฝ์šฐ) true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์ถœ๋ ฅ์ด ๋œ๋‹ค!!
  • isLoading์€ ์บ์‹œ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ณ  ํŽ˜์น˜์ค‘์ผ ๋•Œ true๋ฅผ ๋ฐ˜ํ™˜ํ•จ!(๋‘๊ฐ€์ง€๊ฐ€ ์ถฉ์กฑ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒƒ)

โ— mutations(๋ณ€์ด)

  • ๋ณ€์ด๋Š” ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ์„œ๋ฒ„์— ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์„ ์‹ค์‹œํ•œ๋‹ค.

โœจ useMutaion

  • mutateํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ด ํ•จ์ˆ˜๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ํ† ๋Œ€๋กœ ์„œ๋ฒ„๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. // deleteMutation.mutate(post.id)
  • ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ฟผ๋ฆฌ ํ‚ค๋Š” ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค.
  • isLoading์€ ์กด์žฌํ•˜์ง€๋งŒ isFetching์€ ์—†๋‹ค.
  • ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์‹œ๋„ํ•˜์ง€ ์•Š์ง€๋งŒ ์ž๋™ ์žฌ์‹œ๋„๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ”๏ธ delete

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

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));
...
return (
  // useMutation์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์˜ mutate๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค!
  // mutate๋ฉ”์„œ๋“œ์— ์ธ์ž๋กœ post.id๋ฅผ ์ „๋‹ฌํ•˜์—ฌ, post.id๊ฐ€ ์ฝœ๋ฐฑํ•จ์ˆ˜์˜ ์ธ์ž๋กœ ๋“ค์–ด๊ฐ€ fetchํ•จ์ˆ˜์— ์ „๋‹ฌํ•œ๋‹ค.
  <button onClick={() => deleteMutation.mutate(post.id)}>Delete</button>
  // isError, isLoading, isSuccess ํ”„๋Ÿฌํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  {deleteMutation.isError && (
     <p style={{ color: "red" }}>Error deleting the post</p>
  )}
  {deleteMutation.isLoading && (
     <p style={{ color: "purple" }}>Deleting the post</p>
  )}
  {deleteMutation.isSuccess && (
     <p style={{ color: "green" }}>Post has (not) been deleted</p>
  )}
);

โœ”๏ธ update

  • delete์™€ ๋น„์Šทํ•œ ๋กœ์ง์ด๋‹ค.
async function updatePost(postId) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/postId/${postId}`,
    { method: "PATCH", data: { title: "REACT QUERY FOREVER!!!!" } }
  );
  return response.json();
}
...
const updateMutation = useMutation((postId) => updatePost(postId));

return (
<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>
  )}
);
profile
์ฆ๊ฒ๊ฒŒ ๊ฐœ๋ฐœํ•˜์ž ์ฅฌ์•ผํ˜ธ๐Ÿ‘ป
post-custom-banner

0๊ฐœ์˜ ๋Œ“๊ธ€