[React-Query] useMutation

스머리·2024년 3월 11일
0

React-Query

목록 보기
5/6

데이터베이스에 새로운 값을 추가하거나 수정, 삭제하는 행위는 사이드 이펙트에 해당한다. 그리고 이렇게 사이드 이펙트가 발생하는 경우에 useMutation()이라는 훅을 사용한다.

useMutation()useQuery()와 차이점이 있다. useQuery()의 쿼리 함수는 컴포넌트가 마운트되면서 자동으로 실행되지만, useMutation()은 실제로 뮤테이션하는 함수를 직접 실행해 줘야 한다. mutate()함수를 통해 mutationFn으로 등록했던 함수를 실행할 수 있고, 그래야만 백엔드 데이터를 실제로 수정하게 된다.

참고로 mutate()를 하면 백엔드의 데이터는 변경이 되지만, 현재 캐시에 저장된 데이터는 refetch를 하지 않는 이상 기존의 데이터가 그대로 저장되어 있다. 따라서 refetch를 해줘야만 변경된 데이터를 화면에 제대로 반영할 수 있다.

useMutation()으로 데이터 추가하기

// 포스트 업로드를 요청하는 API 함수
export async function uploadPost(newPost) {
  const response = await fetch(`${BASE_URL}/posts`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(newPost),
  });

  if (!response.ok) {
    throw new Error('Failed to upload the post.');
  }

  return await response.json();
}
  • 포스트 업로드를 요청하는 API 함수
// HomePage.js
const [content, setContent] = useState('');

// ...

const handleInputChange = (e) => {
  setContent(e.target.value);
}

const handleSubmit = (e) => {
  e.preventDefault();
  const newPost = { username: 'codeit', content };
    // ...
};

return (
  <>
    <div>
      <form onSubmit={handleSubmit}>
        <textarea
          name='content'
          value={content}
          onChange={handleInputChange}
        />
        <button
          disabled={!content}
          type='submit'
        >
          업로드
        </button>
      </form>
    </div>
    <div>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            {post.user.name}: {post.content}
          </li>
        ))}
      </ul>
    </div>
  </>
);
  • 간단한 form을 만들어 새로운 포스트를 입력받을 수 있게 작성
const uploadPostMutation = useMutation({
  mutationFn: (newPost) => uploadPost(newPost),
});

const handleSubmit = (e) => {
  e.preventDefault();
  const newPost = { username: 'codeit', content };
  uploadPostMutation.mutate(newPost);
  setContent('');
};
  • useMutation()을 작성하고, 업로드 버튼을 눌렀을 때 mutate() 함수를 실행하도록 함
  • 하지만 캐시에 있는 데이터가 업데이트 되지 않아, 새로운 데이터를 확인하려면 새로고침을 해줘야 함.

invalidateQueries() 함수

위와 같은 상황에서 쿼리 클라이언트의 invalidateQueries()라는 함수를 사용하면 업로드가 끝난 이후에 자동으로 refetch를 하도록 설정할 수 있다.

invalidateQueries()는 말 그대로 캐시에 있는 모든 쿼리 혹은 특정 쿼리들을 invalidate하는 함수이다. (캐시에 저장된 쿼리를 '무효화한다'는 의미)
쿼리를 invalidate하면 해당 쿼리를 통해 받아 온 데이터를 stale time이 지났는지 아닌지에 상관없이 무조건 stale 상태로 만들고, 해당 데이터를 백그라운드에서 refetch하게 된다.

쿼리 클라이언트는 useQueryClient() 훅을 사용해서 가져올 수 있고, 원하는 시점에 queryClient.invalidateQueries() 함수를 실행하면 된다.

import { useQueryClient } from '@tanstack/react-query'

const queryClient = useQueryClient();

// ...

queryClient.invalidateQueries();
  • 새로 데이터를 추가했을 때, 해당 쿼리를 invalidate하면 데이터를 자동으로 refetch하고, 그러면 새롭게 업로드 된 포스트도 바로 보여 줄 수 있다.

useMutation() 함수의 콜백 옵션

그럼 언제 쿼리를 invalidate해야 할까? 뮤데이션 객체에는 onMutate, onSuccess, onError, onSettled와 같은 주요 옵션들이 있어서 뮤테이션 사이클에 따라 적절한 동작을 추가할 수 있다.
뮤테이션이 성공한 시점(onSuccess)에 ['post']쿼리를 invalidate해 주는 함수를 콜백으로 등록해 주면 된다.

const queryClient = useQueryClient();

// ...

const uploadPostMutation = useMutation({
  mutationFn: (newPost) => uploadPost(newPost),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['posts'] });
  },
});

mutate() 함수의 콜백 옵션

onSuccess, onError, onSettled와 같은 옵션은 useMutation()에서도 사용할 수 있고 mutate() 함수에서도 사용할 수 있다. 이때 useMutation()에 등록한 콜백 함수들이 먼저 실행되고, 그다음에 mutate()에 등록한 콜백 함수들이 실행된다.

const uploadPostMutation = useMutation({
  mutationFn: (newPost) => uploadPost(newPost),
  onSuccess: () => {
    console.log('onSuccess in useMutation');
  },
  onSettled: () => {
    console.log('onSettled in useMutation');
  },
});

...

uploadPostMutation.mutate(newPost, {
  onSuccess: () => {
    console.log('onSuccess in mutate');
  },
  onSettled: () => {
    console.log('onSettled in mutate');
  },
});

한 가지 주의할 점은 useMutaion()에 등록된 콜백 함수들은 컴포넌트가 언마운트되더라도 실행이 되지만, mutate()의 콜백 함수들은 만약 뮤테이션이 끝나기 전에 해당 컴포넌트가 언마운트되면 실행이 되지 않는다.
따라서 query invalidation과 같이 뮤테이션 과정에서 꼭 필요한 로직은 useMutation()을 통해 등록하고, 그 외에 다른 페이지로 리다이렉트한다든가, 혹은 결과를 토스트로 띄워주는 것과 같이 해당 컴포넌트에 종속적인 로직은 mutate()를 통해 등록해 주면 된다.

...

const uploadPostMutation = useMutation({
  mutationFn: (newPost) => uploadPost(newPost),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['posts'] });
  },
});

const handleUploadPost = (newPost) => {
  uploadPostMutation.mutate(newPost, {
    onSuccess: () => {
      toast('포스트가 성공적으로 업로드 되었습니다!');
    },
  });
};

isPending 프로퍼티 활용하기

포스트가 업로드되는 중에는 중복해서 업로드 되면 안 되므로 버튼을 비활성화한다.
뮤테이션에는 isPending이라는 값이 있다. 아래와 같이 uploadPostMutation.isPending 값을 이용하면 간단히 구현할 수 있다.

const uploadPostMutation = useMutation({
  mutationFn: (newPost) => uploadPost(newPost),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['posts'] });
  },
});

// ...

<button
  disabled={uploadPostMutation.isPending || !content}
  type='submit'
>
  업로드
</button>

profile
꾸준히 나아가는 프론트엔드 개발자

0개의 댓글

관련 채용 정보