TypeScript

[React Query] useMutation과 useQuery

코샵 2024. 12. 24. 10:03
반응형

소개

React Query의 useQueryuseMutation은 서버 상태 관리를 위한 핵심 훅입니다. 이번 글에서는 두 훅의 상세한 사용법과 실전 예제를 알아보겠습니다.

설치

# npm
npm install @tanstack/react-query

# yarn
yarn add @tanstack/react-query

# pnpm
pnpm add @tanstack/react-query

useQuery 기본 사용법

간단한 데이터 조회

import { useQuery } from '@tanstack/react-query';

// 사용자 조회 훅
function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    staleTime: 5 * 60 * 1000,  // 5분
    cacheTime: 30 * 60 * 1000, // 30분
  });
}

// 컴포넌트에서 사용
function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error } = useUser(userId);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
}

의존적 쿼리

function UserPosts({ userId }: { userId: string }) {
  const { data: user } = useUser(userId);

  const { data: posts } = useQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchUserPosts(userId),
    enabled: !!user, // user 데이터가 있을 때만 실행
  });

  return (
    <div>
      {posts?.map(post => (
        <PostItem key={post.id} post={post} />
      ))}
    </div>
  );
}

useMutation 기본 사용법

데이터 수정

function CreateUserForm() {
  const mutation = useMutation({
    mutationFn: (newUser: User) => {
      return axios.post('/api/users', newUser);
    },
    onSuccess: (data) => {
      console.log('User created:', data);
      // 캐시 무효화 또는 업데이트
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    mutation.mutate({ name: 'John', email: 'john@example.com' });
  };

  return (
    <form onSubmit={handleSubmit}>
      {mutation.isLoading ? (
        'Creating user...'
      ) : (
        <>
          {mutation.isError ? (
            <div>Error: {mutation.error.message}</div>
          ) : null}
          {mutation.isSuccess ? <div>User created!</div> : null}
          <button type="submit">Create User</button>
        </>
      )}
    </form>
  );
}

고급 사용법

낙관적 업데이트

const updateUserMutation = useMutation({
  mutationFn: updateUser,
  onMutate: async (newUserData) => {
    // 진행 중인 쿼리 취소
    await queryClient.cancelQueries(['user', newUserData.id]);

    // 이전 데이터 저장
    const previousUser = queryClient.getQueryData(['user', newUserData.id]);

    // 낙관적 업데이트
    queryClient.setQueryData(['user', newUserData.id], newUserData);

    // 롤백을 위한 컨텍스트 반환
    return { previousUser };
  },
  onError: (err, newUserData, context) => {
    // 에러 발생 시 롤백
    queryClient.setQueryData(
      ['user', newUserData.id],
      context.previousUser
    );
  },
  onSettled: (newUserData) => {
    // 캐시 무효화
    queryClient.invalidateQueries(['user', newUserData.id]);
  },
});

무한 스크롤

function InfinitePostList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam),
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  });

  return (
    <div>
      {data?.pages.map((page, i) => (
        <React.Fragment key={i}>
          {page.posts.map(post => (
            <PostItem key={post.id} post={post} />
          ))}
        </React.Fragment>
      ))}
      <button
        onClick={() => fetchNextPage()}
        disabled={!hasNextPage || isFetchingNextPage}
      >
        {isFetchingNextPage
          ? 'Loading more...'
          : hasNextPage
          ? 'Load More'
          : 'Nothing more to load'}
      </button>
    </div>
  );
}

실시간 데이터 동기화

function LiveUserData({ userId }: { userId: string }) {
  const { data } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    refetchInterval: 1000, // 1초마다 갱신
  });

  return <div>{data?.name}</div>;
}

성능 최적화

선택적 데이터 변환

const { data } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
  select: (users) => {
    return users
      .filter(user => user.isActive)
      .sort((a, b) => b.lastActiveAt - a.lastActiveAt);
  },
});

병렬 쿼리

function ParallelQueries() {
  const users = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
  const posts = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });

  return (
    <div>
      {users.data?.length} users
      {posts.data?.length} posts
    </div>
  );
}

에러 처리

function ErrorBoundaryExample() {
  const { data, error, isError } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
    retry: 3,
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
    onError: (error) => {
      // 에러 로깅
      console.error('Query error:', error);
    },
  });

  if (isError) {
    return <ErrorComponent error={error} />;
  }

  return <div>{data}</div>;
}

마치며

useQueryuseMutation은 React 애플리케이션에서 서버 상태를 관리하는 강력한 도구입니다. 적절한 설정과 최적화를 통해 효율적인 데이터 관리가 가능합니다.