[React] React-Query(3) - useMutation()

2024. 11. 15. 10:58·React

☑️ useMutation이란?

Mutation : 돌연변이, 형태나 구조상의 변화

 

React Query의 useMutation 훅은 데이터베이스에 새로운 값을 추가하거나 기존 데이터를 수정 및 삭제하는 등 "변경(Mutation)"이 필요한 작업을 수행할 때 사용한다. 이와 같은 작업은 사이드 이펙트를 발생시키며, 이러한 사이드 이펙트를 다루기 위해 useMutation을 사용한다.

 

✔️ useMutation()와 useQuery()의 차이

  • useQuery()의 쿼리 함수는 컴포넌트가 마운트될 때 자동으로 쿼리 함수를 실행해 데이터를 가져온다.
  • useMutation()은 실제로 Mutation하는 함수를 직접 실행해 줘야 한다. mutate() 함수를 통해 mutationFn으로 등록했던 함수를 실행할 수 있고, 백엔드 데이터를 실제로 수정하게 된다.
    • mutate()를 하면 백엔드 데이터는 변경되지만, 캐시에 저장된 데이터는 refetch를 하지 않는 이상 기존의 데이터가 그대로 저장되어있다.

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

import { useState } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { getPosts, uploadPost } from './api';

function HomePage() {
  const [content, setContent] = useState('');
  const {
    data: postsData,
    isPending,
    isError,
  } = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    retry: 0,
  });

  const uploadPostMutation = useMutation({
    mutationFn: (newPost) => uploadPost(newPost),
  });

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

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

  if (isPending) return '로딩 중입니다...';

  if (isError) return '에러가 발생했습니다.';

  const posts = postsData?.results ?? [];

  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>
    </>
  );
}

export default HomePage;

위의 예제는 포스트 업로드 하는 기능을 만든 것이다. useMutation()을 작성하고, 업로드 버튼(handleSubmit)을 눌렀을 때mutate() 함수를 실행하도록 해줬다.


☑️ invalidateQueries() 데이터 바로 업데이트하기

useMutation() 훅을 이용해 새로운 데이터를 추가하지만, 캐시에 있는 데이터가 업데이트되지 않아서 확인하려면 새로고침을 해줘야한다. 이럴 때 쿼리 클라이언트의 invalidateQueries() 함수를 사용하면 업로드가 끝난 이후에 자동으로 refetch를 하도록 설정할 수 있다.


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

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

const queryClient = useQueryClient();

// ...

queryClient.invalidateQueries();

쿼리 클라이언트는 useQueryClient() 훅을 사용해서 가져올 수 있고, 원하는 시점에 queryClient.invalidateQueries() 함수를 실행하면 된다. 따라서 새로 데이터를 추가했을 때, 해당 쿼리를 invalidate하면 데이터를 자동으로 refetch할 수 있게되고, 새롭게 업로드 된 포스트도 바로 보여줄 수 있다.


☑️useMutation() 함수의 콜백 옵션

Mutation 객체는 onMutate, onSuccess, onError, onSettled 와 같은 주요 옵션들이 있어서 Mutation 사이클에 따라 적절한 동작을 추가할 수 있다. 아래 예제에선 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');
  },
});

💡주의할 점!
useMutation()에 등록된 콜백 함수들은 컴포넌트가 언마운트되더라도 실행이 되지만, 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 로 이용하여 버튼을 비활성화 할 수 있다.

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

'React' 카테고리의 다른 글

[React] React-Query(2) - useQuery와 쿼리 상태, 캐싱, 로딩과 에러 처리  (2) 2024.11.13
[React] React-Query(1) - 소개 & 설치  (5) 2024.11.08
'React' 카테고리의 다른 글
  • [React] React-Query(2) - useQuery와 쿼리 상태, 캐싱, 로딩과 에러 처리
  • [React] React-Query(1) - 소개 & 설치
FE Dev. 굼지
FE Dev. 굼지
굼지의 웹 개발 레시피 입니다.
  • FE Dev. 굼지
    굼지의 웹 개발 레시피
    FE Dev. 굼지
    • 분류 전체보기 (14)
      • FrontEnd (3)
      • React (3)
      • Next (8)
      • React-Native (0)
  • 블로그 메뉴

    • 홈
    • 태그
  • 최근 글

FE Dev. 굼지
[React] React-Query(3) - useMutation()
상단으로

티스토리툴바