Mutation - Tanstack Query(2)

bloom74·2024년 6월 3일

Mutation

쿼리와 달리 일반적으로 데이터를 생성/업데이트/삭제하거나의 서버 사이드 이팩트를 수행하는데 사용된다. 이를 위해 Tanstack query는 useMutation 훅을 내보낸다.

즉, 쿼리는 불라오는데 목적을 두고 Mutation은 생성, 업데이트, 삭제를 위해 이용된다.

function App() {
  const mutation = useMutation({
    mutationFn: (newTodo) => {
      return axios.post('/todos', newTodo)
    },
  })

  return (
    <div>
      {mutation.isPending ? (
        'Adding todo...'
      ) : (
        <>
          {mutation.isError ? (
            <div>An error occurred: {mutation.error.message}</div>
          ) : null}

          {mutation.isSuccess ? <div>Todo added!</div> : null}

          <button
            onClick={() => {
              mutation.mutate({ id: new Date(), title: 'Do Laundry' })
            }}
          >
            Create Todo
          </button>
        </>
      )}
    </div>
  )
}

mutation은 어떤 상황에서도 아래 항목 중 하나의 상태를 가져야 한다.

  • isIdle || status === idle → mutation은 현재 유휴 상태이거나 새로 고침//리셋 상태이다.
  • isPending || status === ‘pending’ → mutation이 동작중입니다.
  • isError || status === error → mutation에서 에러가 발생함
  • isSuccess || status === success → mutation은 성공적이고, 데이터가 사용가능한 상태

기본적인 상태외에도, 상태에 따라 추가적으로 얻을 수 있는 정보들이 있다.

  • error → 오류인 경우 오류 속성들을 통해 오류를 확인할 수 있다.
  • data → 상태가 success인 경우, data 프로퍼티를 사용할 수 있다.

mutation 함수에 변수를 포함해서 단일 변수나 객체로 호출 가능하다는 것을 알 수 있다.

[!info]
리엑트 16 버전 이하는 mutation이 비동기 함수를 반환하기 떄문에, event callback에 할당할 수 없다. event callback에 mutation을 react 16이하 버전에서 추가하고자 한다면, mutate를 다른 함수로 감싸야 한다.

// This will not work in React 16 and earlier
const CreateTodo = () => {
  const mutation = useMutation({
    mutationFn: (event) => {
      event.preventDefault()
      return fetch('/api', new FormData(event.target))
    },
  })

  return <form onSubmit={mutation.mutate}>...</form>
}

// This will work
const CreateTodo = () => {
  const mutation = useMutation({
    mutationFn: (formData) => {
      return fetch('/api', formData)
    }, //화살표 함수
  })
  const onSubmit = (event) => {
    event.preventDefault()
    mutation.mutate(new FormData(event.target))
  }

  return <form onSubmit={onSubmit}>...</form>
}

Mutation 상태 초기화하기

때때로 변경 요청의 오류나 데이터를 지워야 하는 경우가 있다. 이 경우 재설정 기능을 사용하여 이를 처리할 수 있다.

const CreateTodo = () => {
  const [title, setTitle] = useState('')
  const mutation = useMutation({ mutationFn: createTodo })

  const onCreateTodo = (e) => {
    e.preventDefault()
    mutation.mutate({ title })
  }

  return (
    <form onSubmit={onCreateTodo}>
      {mutation.error && (
        <h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
      )}
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <br />
      <button type="submit">Create Todo</button>
    </form>
  )
}

Mutation Side Effects

useMutation은 mutation 수명 주기 중 어느 단계에서든 빠르고 쉽게 부작용을 처리할 수 있는 몇 가지 헬퍼 옵션이 제공된다. 이러한 옵션은 mutation 후 쿼리 무효화 및 refetch 하기, 심지어 낙관적인 업데이트에도 유용하다.

promise를 반환하는 경우,

useMutation({
  mutationFn: addTodo,
  onMutate: (variables) => {
    // A mutation is about to happen!

    // Optionally return a context containing data to use when for example rolling back
    return { id: 1 }
  },
  onError: (error, variables, context) => {
    // An error happened!
    console.log(`rolling back optimistic update with id ${context.id}`)
  },
  onSuccess: (data, variables, context) => {
    // Boom baby!
  },
  onSettled: (data, error, variables, context) => {
    // Error or success... doesn't matter!
    // try...catch...finally에서 finally와 같은 역할을 한다. 
  },
})

mutate를 호출할 때, useMutation에 정의된 콜백 외에 추가 콜백을 트리거하고 싶을 수 있다. 이는 component-specific side-effects을 트리거 할때 사용될 수 있다. 이를 위해서는 mutate 변수 뒤에 동일한 콜백 옵션을 mutate함수에 제공하면 된다. 지원하는 옵션으로는 onSuccess, onError, onSettled가 있다.

useMutation, mutation callback 차이

  • useMutation은 onSuccess, onError, onSettled와 같은 Callback 함수들을 가질 수 있다.
  • 그뿐만 아니라, mutate 역시 위와 같은 Callback 함수들을 가질 수 있다.
  • 둘의 동작은 같다고 생각할 수 있지만 약간의 차이가 있다. 다음과 같다.
    • useMutation의 Callback 함수와 mutate의 Callback 함수는 독립적으로 실행된다.
    • 순서는 useMutation의 Callback -> mutate의 Callback 순으로 실행된다.
    • mutation이 완료되기 전에 컴포넌트가 unmount된다면 mutate의 Callback은 실행되지 않을 수 있다.
  • TkDodo는 위와 같은 이유로 둘을 분리해서 사용하는 것이 적절하다고 한다.
    • 꼭 필요한 로직(ex. 쿼리 초기화)은 useMutation의 Callback으로 실행시킨다.
    • 리다이렉션 및 UI 관련 작업은 mutate Callback에서 실행시킨다. 참고글

mutate와 mutateAsync

  • 대부분의 경우 mutate를 사용하는 것이 유리하다. mutate는 콜백을 통해 data와 error에 접근할 수 있기 때문에 특별히 핸들링 해줄 필요가 없다.
  • promise 형태의 resolve, reject 형태의 반환이 필요한 상황에서는, mutateAsync를 mutate 대신 사용해야 한다.
    - 에러 핸들링 같은 부분을 직접 다뤄야 한다는 번거로움이 있다.

Query와 함께 사용되는 "invalidateQueries"

  • invalidateQueries는 화면을 최신상태로 유지하는 가장 간단한 방법이다.
  • 예를 들면, 게시판 목록에서 어떤 게시글은 Post하거나 게시글을 제거했을때 화면에 보여주는 게시판 목록을 실시간으로 최신화해야 할 때가 있다.
  • 하지만 이때, query key가 변하지 않으므로 강제로 쿼리를 무효화하고 최신화를 진행해야 하는데, 이런 경우 invalidateQueries() 메소드를 이용할 수 있다.
  • 즉, query가 오래되었다는 것을 판단하고 다시 refetch를 할때 사용한다.
import { useMutation, useQuery, useQueryClient} from "@tanstack/react-query";

const useAddSuperHeroData = () => {
	const queryClient = useQueryClient();
	
	return useMutation(addSuperHero, {
		onSuccess(data) {
				queryClient.invalidateQueries({queryKey: ["super-heros"] });
				console.log(data);
			}
			
		onError(err) {
			console.log(err);	
		}
		});
};

위 예제같은 경우는 querykey에 "super-heroes"를 넘겨주면, querykey에 "super-heroes"를 포함하는 모든 쿼리가 무효화된다.

0개의 댓글