React 44장 - React-Query (2)
포스트
취소

React 44장 - React-Query (2)

React

React Query

1. Pagenation

  • 이전 글에서 작동했던 것처럼 의존성 배열을 추가하여, 데이터를 가진 페이지의 번호가 바뀔 때마다 새로운 데이터들을 불러오도록 지정하여 button에 대한 이벤트를 처리할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function fetchPosts() {
  const response = await fetch("http://example.com/posts?limit=10&page=1");
  return response.json();
}

function Post() {
  const [pageNum, setPageNum] = useState(0);

  const { data, isLoading, isError, error } = useQuery("post", fetchPosts);

  return (
    <>
      ...중략
      <button>Previous Page</button>
      <button>Next Page</button>
    </>
  );
}
  • 기존에 작성된 코드에서 의존성 배열을 추가해주고, fetchPosts에 pageNum에 관한 정보를 넘겨주어 pageNum에 해당하는 페이지에 들어있는 데이터들을 받아오도록 한다.
  • post에 관한 각 댓글의 목록을 받아오는 방식과 똑같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Post() {
  const [pageNum, setPageNum] = useState(1);
  const maxPage = 10;

  const { data, isLoading, isError, error } = useQuery(["post", pageNum], () =>
    fetchPosts(pageNum)
  );

  return (
    <>
      ...중략
      <button disable={pageNum <= 0} onClick={() => setPageNum(pageNum - 1)}>
        Previous Page
      </button>
      <button disable={pageNum >= 10} onClick={() => setPageNum(pageNum + 1)}>
        Next Page
      </button>
    </>
  );
}

async function fetchPosts(pageNum) {
  const response = await fetch(
    `http://example.com/posts?limit=10&page=${pageNum}`
  );
  return response.json();
}

2.Prefetching

  • App 컴포넌트에서 선언한 queryClient의 메서드인 prfetch 쿼리를 가져온다.
  • 가져온 훅을 기능 컴포넌트에서 선언 후 사용한다.
  • isLoading 을 사용하여 page가 바뀔 때마다 매번 새로운 데이터를 요청하고 이를 기다리는 과정을 반복하면 사용자 경험에 좋지 않은 영향을 끼친다.
  • 따라서 page가 바뀔 때마다 다음 페이지에 대한 데이터를 미리 받아오는 작업을 거치고, 이 데이터를 캐시해놓아 사용자가 다음 page를 눌렀을 떄 캐시된 데이터를 미리 보여주는 방식을 사용한다.
1
2
3
4
5
6
7
8
9
10
11
import { useQuery, useQueryClient } from "react-query";

function Posts() {
  const queryClient = useQueryClient();
  useEffect(() => {
    if(pageNUm < maxPage>){
      const nextPage = pageNum + 1;
      queryClient.prefetchQuery(["posts", nextPage], () => fetch(nextPage));
    }
  }, [pageNum, queryClient]);
}
  • pageNum이 바뀔 때마다 nexPage에 대한 데이터를 미리 가져온다.
  • prefetch로 사용할 key 값은 우리가 이전에 사용한 fetch의 키와 동일하다.
  • 우리가 이전 페이지로 돌아갔을 때도 데이터를 유지하도록 설정을 추가한다.
1
2
3
4
5
6
7
8
const { data, isLoading, isError, error } = useQuery(
  ["posts", pageNum],
  () => fetchPosts(pageNum),
  {
    staleTime: 2000,
    keepPreviousData: true,
  }
);

3. isFetching

  • isLoading과 마찬가지로 isFetching 또한 사용할 수 있다.
  • isFetching은 isLoading을 포함하고 있으며, isLoading은 쿼리에 대해 캐시된 데이터가 없는 상태를 뜻한다.
  • 위에서 prefetch를 사용하여 미리 데이터를 받아왔다고 하더라도 캐시된 존재 여부에 관계 없이 prefetch 이전에 실행되기 떄문에 isFetching을 사용할 경우 항상 참이 된다.

4. Mutations

  • useMutation을 사용하여 데이터를 삭제, 추가 등의 역할을 한다.
  • 쿼리를 관리하며 캐시를 업데이트하고 자동으로 리렌더링을 처리한다.
  • useQuery와 마찬가지로 몇 가지 인자를 받는다.
  • mutate는 실행될 비동기 함수로 promise를 반환하며 api에 요청을 하는 함수이다.
    • 아래의 코드에서는 mutate로 addTodo를 실행시킨다.
  • isLoading, isError, isSuccess를 사용하여 데이터를 기다리고 요청이 성공적으로 수행됐을 때의 동작들을 해결한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const addTodo = async (newTodo) => {
  const { data } = await axios.post("/todos", newTodo);
  return data;
};

const { mutate, isLoading, isError, error, isSuccess } = useMutation(addTodo);

return (
  <>
    {isLoading && <div>isLoading...</div>}
    {isError && <div>Error...</div>}
    {isSuccess && <div>Add data...</div>}
    <button onClick={mutate({ id: 1, todo: "add todo" })}>추가하기</button>
  </>
);

4.1 Test

  • isLoading, isError, error는 useQuery에서 사용 중이기 때문에 또 다시 구조분해할당을 할 필요가 없다.
  • useMutation은 변경된 데이터를 캐시할 필요가 없기 때문에 쿼리 키가 필요하지 않다.
  • 내장 함수인 mutate를 사용해 useMutation을 실행시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
async function fetchComments(postId) {
  const res = await axios.get(`http://localhost:3000/post/${postId}`);
  return res.json();
}

async function deletePost(postId) {
  const res = await axios.delete(`http://localhost:3000/post/${postId}`);
  return res.json();
}

async function updatePost(postId) {
  const res = await axios.post(`http://localhost:3000/post/${postId}`);
  return res.json();
}

function PostDetail({ post }) {
  const { data, isLoading, isError, error } = useQuery("showComments", () =>
    fetchComments(post.id)
  );
  const deleteData = useMutation((postId) => deletePost(postId));
  const updateData = useMutation((postId) => updatePost(postId));

  return (
    <>
      <button onClick={deleteData.mutate(post.id)}>delete</button>
      <button onClick={updateData.mutate(post.id)}>update</button>
      {deleteData.isLoading && <div>delete loading...</div>}
      {deleteData.isError && <div>delete error...</div>}
      {updateData.isLoading && <div>update loading...</div>}
      {updateData.isError && <div>update error...</div>}
    </>
  );
}

정리

  • QueryClient, QueryProvider
    • 최상위 컴포넌트에서 컴포넌트들을 감싸, 하위 컴포넌트에서 query를 사용할 수 있도록 한다.
  • useQuery
    • isLoading,isError 등으로 사용자에게 상태를 제공한다.
    • 매번 isLoading,isError가 실행되면 사용자 경험에 악영향을 끼치기 때문에 isFetching 활용한다.
  • staleTime, cacheTime
    • 데이터가 만료되어 새롭게 데이터를 가져올 주기와 데이터가 만료되어 삭제될 주기를 정한다.
  • useMutation
    • 데이터의 변이를 말하며 데이터를 전송하거나 삭제하는 동작을 한다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.