React-Query 비동기 작업 관리(useMutation())

SUN·2024년 7월 22일

React-query

목록 보기
5/9

1. 개요

서버에 데이터를 생성, 업데이트 또는 삭제하는 비동기 작업을 관리하는 데 사용

Mutation은 형태나 구조상의 변화라는 뜻으로 데이터 베이스에 새로운 값이 추가되거나 수정, 삭제가 되는 사이트 이펙트가 발생하는 경우에 해당 훅을 사용

  • useMutation은 뮤테이션하는 함수를 실행해주어야한다.
  • 캐시 데이터는 변경되지 않아 refetch를 해주어야한다.

2. 사용법

  • api 불러오기(api.js)
import axios from 'axios';

export async function uploadPost(newPost) {
  const response = await axios.post(`${BASE_URL}/posts`, newPost, {
    headers: {
      'Content-Type': 'application/json',
    },
  });

  return response.data;
}

post 업로드를 요청한다.

  • Form
const [content, setContent] = useState('');

// ...

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

const handleSubmit = (e) => {
  e.preventDefault();
  const newPost = { username: 'name', 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을 만들어 새로운 포스트를 입력한다.

textarea에 데이터를 입력하면 content에 업데이트가 되고 button 태그가 활성화가 된다. 그리고 button태그를 클릭하면 handleSubmit 함수가 발동된다.

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

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

useMutation 함수를 작성하면 button 태그를 눌러 handleSubmit가 발동될 때 mutate 함수가 실행되게 한다.

  • 전체 태그
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 설명

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

useMutation 훅을 uploadPostMutation이름으로 정의하고 mutationFn으로 함수를 정의한다.

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

uploadPostMutation.mutate(newPost); 이것은 newpost를 매개로 mutationFn에 정의한 함수를 불러와 실행시킨다. (mutate의 역할)

mutate는 정의된 함수를 실행시키고 두번째 객체에서는 onSuccess, onError, onSettled 콜백을 정의할 수 있다.

하지만 위 함수대로 적용하면 캐시는 변하지 않아 새로고침을 해야 데이터가 반영된다.
그래서 invalidateQueries() 함수가 필요하다.

3. invalidateQueries()

업로드가 끝난 이후에 자동으로 refetch를 하도록 설정

캐시에 저장된 쿼리를 무효화하고 steal time과 상관없이 stale 상태로 만들고 데이터를 refetch한다.

사용법

데이터를 추가했을 때 해당 쿼리를 invaliate하면 refetch를 할 수 있게 된다.

1. useQueryClient() 훅 사용

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

const queryClient = useQueryClient();

// ...

queryClient.invalidateQueries();

useQueryClient() 훅을 사용하여 위 코드와 같이 쿼리 클라이언트를 가져오고
queryClient.invalidateQueries();로 실행한다.

2. useMutation() 함수의 콜백 옵션

useMutation() 함수의 콜백 옵션에서 invaliate의 시점을 정할 수 있다.

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

onMutate, onSuccess, onError, onSettled 등 옵션이 있어 원하는 사이클에 동작하도록 할 수 있다.

3. Mutate() 함수의 콜백 옵션

onSuccess, onError, onSettled와 같은 옵션은 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() 등록된 콜백함수들이 실행된다.

  • useMutation()의 콜백함수 : 컴포넌트가 언마운트되어서 실행, 꼭 필요한 로직에서 사용

  • mutate()의 콜백 함수 : 뮤테이션이 끝나기전 해당 컴포넌트가 언마운트되면 실행되지 않음. 다른 페이지로 리다이렉트나 결과를 띄워주는 팝업같은 컴포넌트의 종속적인 로직은 mutate()를 통해 사용

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

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

isPending 프로퍼티 사용

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

// ...

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

내용이 업로도 되는 중 중복해서 업로드 되는 것을 방지하기 위해 isPending을 사용하여 업로드 되는 중에 버튼을 비활성화한다.

profile
안녕하세요!

0개의 댓글