무엇
mutationOptions는 한 뮤테이션의 함수·키·콜백을 한 객체로 묶어 돌려주는 헬퍼다. 이 객체를 useMutation에 그대로 넘겨 재사용한다. queryOptions가 쿼리에 해준 일을 뮤테이션에 해준다.
useMutation에 넘길 수 있는 건 대체로 mutationOptions에도 넘길 수 있다. 같은 뮤테이션을 여러 곳에서 쓸 때, 정의를 컴포넌트 밖 한 곳에 모아 둔다.
import { mutationOptions } from '@tanstack/react-query'
function updateGroupOptions() {
return mutationOptions({
mutationKey: ['groups'],
mutationFn: (group: Group) => updateGroup(group),
})
}
무슨 일이 일어나나
mutationOptions는 런타임에서는 넘긴 객체를 사실상 그대로 돌려준다. 값어치는 타입과 한 곳 정의에 있다. 뮤테이션 함수의 입력·출력 타입이 반환 객체에 새겨져서, 이 옵션을 useMutation에 넘기면 mutate의 인자 타입과 data 타입이 자동으로 잡힌다. 제네릭을 손으로 적지 않아도 된다.
queryOptions와 짝을 이루지만 쓰임새는 조금 다르다. 쿼리는 키로 캐시를 공유하기 때문에 옵션을 여러 API(useQuery·prefetchQuery·setQueryData)가 나눠 쓴다. 뮤테이션은 캐시에 저장되지 않아서, mutationOptions가 주는 이득은 주로 정의 재사용과 타입 추론이다.
사용법
한 번 정의해 여러 컴포넌트에서 같은 뮤테이션을 쓴다. 옵션 정의를 파일 하나로 빼 두고 훅에서 불러 쓴다.
import { mutationOptions } from '@tanstack/react-query'
export function updateGroupOptions() {
return mutationOptions({
mutationKey: ['groups'],
mutationFn: (group: Group) => updateGroup(group),
})
}
// 컴포넌트에서
const mutation = useMutation(updateGroupOptions())
mutation.mutate(nextGroup) // nextGroup 타입은 자동으로 Group
호출 지점에서 옵션을 덧붙이고 싶으면 펼쳐서 합친다. 공용 정의는 그대로 두고 화면별 콜백만 얹는 식이다.
const mutation = useMutation({
...updateGroupOptions(),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['groups'] }),
})
실무 예시
목록을 갱신하는 뮤테이션을 여러 화면이 공유하는 경우다. 뮤테이션 함수와 키는 한 곳에 정의하고, 성공 후 무엇을 무효화할지는 호출 지점에서 정한다.
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { updateGroupOptions } from './updateGroupOptions'
export function EditGroup({ group }: { group: Group }) {
const queryClient = useQueryClient()
const { mutate, isPending, isError } = useMutation({
...updateGroupOptions(),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['groups'] })
},
})
return (
<button onClick={() => mutate(group)} disabled={isPending}>
{isPending ? '저장 중…' : '저장'}
{isError && ' — 실패'}
</button>
)
}
왜 중요한가
mutationOptions는 v5에서 queryOptions와 함께 들어온 헬퍼로, 뮤테이션 정의를 컴포넌트 밖으로 빼 재사용할 때 타입까지 이어 주는 얇은 감싸개다. 쿼리 쪽 queryOptions만큼 필수는 아니다 — 뮤테이션은 캐시를 공유하지 않아 정의를 나눠 쓸 API가 적기 때문이다. 같은 뮤테이션을 여러 화면에서 반복해 쓰고 타입을 한 곳에서 관리하고 싶을 때 제값을 한다. 한 컴포넌트 안에서만 쓰는 뮤테이션이라면 useMutation에 옵션을 바로 넘겨도 충분하다.
Reference