[React-Query] mutation

Suvina·2026년 2월 24일

React

목록 보기
23/23

1. Mutatin 이란?

서버의 데이터를 변경 하는 작업

  • useQuery -> GET (데이터 읽기)
  • useMutation -> POST, PUT, DELETE (데이터 변경)

기본 구조 및 실행 옵션

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

강력한 콜백 옵션 (Life Cycle)

요청의 생명주기에 따라 원하는 로직을 주입할 수 있음

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 vs useMutation: 왜 리액트 쿼리를 쓰는 걸까?

❌ 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)

  • 일반 fetch: 컴포넌트 A와 B에서 같은 훅을 써도, 각각 독립된 상태(useState)를 가짐. 즉, A에서 로그인 성공해도 B는 모름
  • React Query: queryClient 라는 거대한 저장소에서 데이터를 관리함. A에서 변경된 데이터가 전역적으로 공유되어 B컴포넌트도 즉시 업데이트 됨

② 똑똑한 재시도 (Auto Retry)

  • 일반 fetch: retry 로직, 횟수 제한, 지수 백오프 등을 직접 구현해야 함
  • React Query: 옵션 한 줄
useMutation({ mutationFn: login, retry: 3 }); // 실패 시 3번까지 자동 재시도

③ 강력한 개발자 도구 (Devtools)
현재 어떤 요청이 pending인지, 어떤 데이터가 캐시되어 있는지 브라우저에서 시각적으로 바로 확인할 수 있다. 디버깅 시간이 획기적으로 단축됩니다.

profile
개인공부

0개의 댓글