서버의 데이터를 변경 하는 작업
useMutation은 실행 시 mutate와 mutateAsync 두 가지 방식을 제공
const { mutate, mutateAsync } = useMutation({ mutationFn: updateData });
// 1. mutate (일반적인 경우)
// 결과와 상관없이 호출 후 바로 다음 코드로 넘어갑니다.
mutate(variables);
// 2. mutateAsync (Promise 반환)
// 비동기 처리가 필요할 때 사용하며, try/catch로 직접 에러 핸들링이 가능합니다.
try {
const data = await mutateAsync(variables);
console.log('성공!', data);
} catch (error) {
console.error('실패!', error);
}
요청의 생명주기에 따라 원하는 로직을 주입할 수 있음
useMutation({
mutationFn: ...,
onSuccess: (data, variables, context) => {
// 성공했을 때
},
onError: (error, variables, context) => {
// 실패했을 때
},
onSettled: (data, error, variables, context) => {
// 성공/실패 상관없이 끝났을 때
},
onMutate: async (variables) => {
// mutate 호출 직후, 서버 요청 전에 실행
// 낙관적 업데이트할 때 주로 씀
}
})
💡 팁: 콜백은 useMutation 정의 시(공통 처리)와 mutate 호출 시(해당 시점 특화 처리) 두 곳 모두 작성 가능
UI 대응을 위해 다양한 상태값을 제공함
const {
mutate,
isPending, // 요청 중 (로딩 스피너에 유용)
isSuccess, // 성공
isError, // 실패
isIdel, // 아직 실행 안됨 (초기 상태)
data, // 서버로부터 받은 성공 응답 데이터
error, // 발생한 에러 객체
reset // 상태 초기화
} = useMutation({ mutationFn: ... })
로그인 로직을 통한 커스텀 훅 구현 사례
export const useLoginMutation = () => {
const queryClient = useQueryClient();
return useMutation({
// 1. 실제 서버 API 호출
mutationFn: (request: LoginRequest) => authApi.login(request),
// 2. 성공 시 후속 작업
onSuccess: (data) => {
if (data.data) {
// 유저 정보를 전역 캐시에 즉시 반영
queryClient.setQueryData(authKeys.user(), data.data);
}
},
onError: (err) => {
alert('로그인에 실패했습니다. 다시 시도해주세요.');
}
});
};
const { mutate, isPending } = useLoginMutation();
const handleSubmit = (e) => {
e.preventDefault();
// 버튼 클릭 시 mutate 호출 -> 서버 요청 발생
mutate({ email, password });
};
return (
<button disabled={isPending}>
{isPending ? '로그인 중...' : '로그인'}
</button>
);
❌ fetch만 사용할 때
매번 loading, error, data 상태를 직접 선언하고 try-catch-finally 문을 반복해야 함
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const login = async () => {
setLoading(true)
try {
const res = await.fetch('api/login', {method: 'POST', body:...})
const data = await res json()
setData(data)
}catch(e){
setError(e)
}finally{
setLoading(false)
}
}
💡 매번 이 boilerplate를 반복해야 함
✅ useMutation을 사용할 때
선언 한 줄로 모든 상태 관리가 끝난다. 로직은 커스텀 훅으로 분리되어 UI 컴포넌트는 깨끗해진다.
const { mutate, isPending, isError } = useLoginMutation()
// UI에서는 변수만 가져다 쓰면 끝!
{isPending && <Spinner />}
useMutation은 직접 만든 훅으로는 구현하기 까다로운 것들을 제공해 줌
① 전역 캐시 관리 (State Sharing)
② 똑똑한 재시도 (Auto Retry)
useMutation({ mutationFn: login, retry: 3 }); // 실패 시 3번까지 자동 재시도
③ 강력한 개발자 도구 (Devtools)
현재 어떤 요청이 pending인지, 어떤 데이터가 캐시되어 있는지 브라우저에서 시각적으로 바로 확인할 수 있다. 디버깅 시간이 획기적으로 단축됩니다.