리액트 쿼리 다양한 사용방법

LEE GYUHO·2024년 1월 16일
0

Dependant Query

  • Dependant Query는 어떤 특정 값이나 조건이 충족된 이후에 실행되는 쿼리이다.

userId 값이 있을 때만 실행되도록 설정


const { data: user } = useQuery({
  queryKey: ['user', email],
  queryFn: getUserByEmail,
});

const userId = user?.id

const {
  data: projects,
} = useQuery({
  queryKey: ['projects', userId],
  queryFn: getProjectsByUser,
  enabled: !!userId,
});

Paginated Query

  • 쿼리 키에 페이지 정보를 포함해서 페이지네이션을 구현할 수 있다.
  • placeholderData 옵션을 사용하면 새로운 페이지를 보여줄 때 이전의 데이터를 보여주다가 새로운 데이터가 오면 자연스럽게 전환할 수 있다.
const {data: postsData } = useQuery({
    queryKey: ['posts', page],
    queryFn: () => getPosts(page, PAGE_LIMIT),
    placeholderData: keepPreviousData,
});
  • prefetchQuery()함수를 이용하면 다음 페이지의 데이터를 미리 fetch할 수 있다.
useEffect(() => {
  if (!isPlaceholderData && postsData?.hasMore) {
    queryClient.prefetchQuery({
      queryKey: ['posts', page + 1],
      queryFn: () => getPosts(page + 1, PAGE_LIMIT),
    });
  }
}, [isPlaceholderData, postsData, queryClient, page]);

Infinite Query

const {
  data: postsData,
  isPending,
  isError,
  hasNextPage,
  fetchNextPage,
  isFetchingNextPage,
} = useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam }) => getPosts(pageParam, LIMIT),
  initialPageParam: 0,
  getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
    lastPage.hasMore ? lastPageParam + 1 : undefined,
});
  • useInfiniteQuery() 훅에서는 page param 값을 활용하여 페이지를 더 불러온다.
  • useInfiniteQuery()에서는 data에 모든 페이지의 데이터가 pages라는 프로퍼티로 배열에 담겨 있다.
  • getNextPageParam() 함수에서 다음 페이지가 있는 경우 다음 page param 값을 리턴한다.
  • fetchNextPage() 함수에서는 이렇게 리턴된 page param 값을 쿼리 함수로 전달해 다음 페이지의 데이터를 받아온다.
  • 만약 getNextPageParams() 함수에서 undefined나 null 값을 리턴하면 다음 페이지가 없는 것으로 간주해 fetchNextPage() 함수를 실행해도 더 이상 데이터를 받아오지 않고, hasNextPage의 값도 false가 된다.

Optimistic Updates

const likeMutation = useMutation({
  mutationFn: async ({ postId, username, userAction }) => {
    if (userAction === USER_ACTION.LIKE_POST) {
      await likePost(postId, username);
    } else {
      await unlikePost(postId, username);
    }
  },
  onMutate: async ({ postId, username, userAction }) => {
    await queryClient.cancelQueries({
      queryKey: [QUERY_KEYS.LIKE_STATUS, postId],
    });
    await queryClient.cancelQueries({
      queryKey: [QUERY_KEYS.NUM_OF_LIKES, postId],
    });

    const prevLikeStatus = queryClient.getQueryData([
      QUERY_KEYS.LIKE_STATUS,
      postId,
      username,
    ]);
    const prevLikeCount = queryClient.getQueryData([
      QUERY_KEYS.LIKE_COUNT,
      postId,
    ]);

    queryClient.setQueryData(
      [QUERY_KEYS.LIKE_STATUS, postId, username],
      () => userAction === USER_ACTION.LIKE_POST
    );
    queryClient.setQueryData([QUERY_KEYS.LIKE_COUNT, postId], (prev) => {
      userAction === USER_ACTION.LIKE_POST ? prev + 1 : prev - 1;
    });

    return { prevLikeStatus, prevLikeCount };
  },
  onError: (err, { postId, username }, context) => {
    queryClient.setQueryData(
      [QUERY_KEYS.LIKE_STATUS, postId, username],
      context.prevLikeStatus
    );
    queryClient.setQueryData(
      [QUERY_KEYS.LIKE_COUNT, postId],
      context.prevLikeCount
    );
  },
  onSettled: (data, err, { postId, username }) => {
    queryClient.invalidateQueries({
      queryKey: [QUERY_KEYS.LIKE_STATUS, postId, username],
    });
    queryClient.invalidateQueries({
      queryKey: [QUERY_KEYS.LIKE_COUNT, postId],
    });
  },
});
  • 서버가 제대로 동작할 것을 낙관적으로 기대하며, 서버로부터의 리스폰스를 기다리지 않고 유저에게 바로 피드백을 주는 방식을 말한다.
  • OnMutate
    • 우리가 옵티미스틱 업데이트를 통해 변경하려고 하는 데이터가 refetch로 인해 덮어씌워지는 것을 막기 위해 cancelQueries()를 실행하여, 좋아요 관련 데이터를 받아 오지 않도록 쿼리를 취소해준다.
    • 에러가 발생했을 때는 이전의 데이터로 롤백해 줘야 해서 롤백용 데이터를 따로 저장해 준다.
    • 우리가 원하는 값으로 쿼리 데이터를 미리 변경한다.
    • 마지막으로 롤백용 데이터를 리턴해주면 된다.
  • onError
    • 롤백용 데이터를 세 번째 파라미터인 context로 받아 온다.
    • context 값으로 쿼리 데이터를 변경해 준다.
  • onSettled
    • 에러 여부와 상관없이 백엔드 서버와 데이터를 동기화해주기 위해 좋아요 관련 데이터 쿼리를 invalidate해준다.
profile
누구나 같은 팀으로 되길 바라는 개발자가 되자

0개의 댓글