Published on

React Query의 mutations 사용하기

React Query 에서 지원하는 mutations 사용방법에 대한 정리

mutation은 react-query의 useMutation 훅을 사용하여 구현하며 create, update, delete API 호출시에 사용한다.
자세한 사용방법은 다음 가이드를 참고한다.
https://react-query.tanstack.com/guides/mutations

useMutation 옵션

  • onMutate : mutation 시작 전에 호출됨. 아래 예제에서 이를 활용하여 Optimistic Update 를 적용하였다.
  • onError : 에러 콜백
  • onSuccess : 성공 콜백
  • onSettled : 성공, 실패 상관없이 요청이 끝났을 때 호출된다.

mutation 객체

useMutation을 호출하면 mutation객체가 리턴된다.
mutation에는 useMutation의 첫 번째 파라미터 함수를 호출하는 mutate가 존재한다.

사용 예제

다음의 사용 예제를 작성하였다.

  • useMutation 사용
  • mutation 완료시 query invalidation 적용
  • mutation response를 통해 query invalidation 하지 않고, query data 업데이트
  • mutation 호출 전에 optimistic update

useSuperHeroesData.js

import { useQuery, useMutation, useQueryClient } from 'react-query';
import axios from 'axios';

const SUPER_HERO_DATA_KEY = 'super-heroes';

const fetchSuperHeroes = () => {
  return axios.get('http://localhost:4000/superheroes');
};

const addSuperHero = (hero) => {
  return axios.post('http://localhost:4000/superheroes', hero);
};

export const useSuperHeroesData = (onSuccess, onError) => {
  return useQuery(SUPER_HERO_DATA_KEY, fetchSuperHeroes, {
    onSuccess,
    onError,
    // select: data => {
    //   const superHeroNames = data.data.map(hero => hero.name)
    //   return superHeroNames
    // }
  });
};

export const useAddSuperHeroData = () => {
  const queryClient = useQueryClient();

  return useMutation(addSuperHero, {
    onSuccess: (data) => {
      /** Query Invalidation Start */
      //queryClient.invalidateQueries(SUPER_HERO_DATA_KEY)
      /** Query Invalidation End */

      /** Handling Mutation Response Start */
      queryClient.setQueryData(SUPER_HERO_DATA_KEY, (oldQueryData) => {
        return {
          ...oldQueryData,
          data: [...oldQueryData.data, data.data],
        };
      });
      /** Handling Mutation Response End */
    },
    /**Optimistic Update Start */
    onMutate: async (newHero) => {
      await queryClient.cancelQueries(SUPER_HERO_DATA_KEY);
      const previousHeroData = queryClient.getQueryData(SUPER_HERO_DATA_KEY);
      queryClient.setQueryData(SUPER_HERO_DATA_KEY, (oldQueryData) => {
        //API 응답전에 미리 업데이트
        return {
          ...oldQueryData,
          data: [...oldQueryData.data, { id: oldQueryData?.data?.length + 1, ...newHero }],
        };
      });
      return { previousHeroData }; //에러 발생시 onError에서 이전 data로 다시 복원하기 위함.
    },
    onError: (_err, _newTodo, context) => {
      queryClient.setQueryData(SUPER_HERO_DATA_KEY, context.previousHeroData);
    },
    onSettled: () => {
      queryClient.invalidateQueries(SUPER_HERO_DATA_KEY); // query invalidation. 서버로부터 다시 갱신
    },
    /**Optimistic Update End */
  });
};

SuperHeroesPage.js

import { useState } from 'react'
import {
  useAddSuperHeroData,
  useSuperHeroesData
} from '../hooks/useSuperHeroesData'
import { Link } from 'react-router-dom'

export const RQSuperHeroesPage = () => {
  const [name, setName] = useState('')
  const [alterEgo, setAlterEgo] = useState('')

  const onSuccess = data => {
    console.log({ data })
  }

  const onError = error => {
    console.log({ error })
  }

  const { isLoading, data, isError, error, refetch } = useSuperHeroesData(
    onSuccess,
    onError
  )

  const {
      mutate: addHero,
      isLoading: isAddHeroLoading,
      isError: isAddHeroError,
      error: addHeroError,
      isSuccess: isAddHeroSuccess
  } = useAddSuperHeroData()

  const handleAddHeroClick = () => {
    const hero = { name, alterEgo }
    addHero(hero)
  }

  if (isLoading) {
    return <h2>Loading...</h2>
  }

  if (isError) {
    return <h2>{error.message}</h2>
  }

  return (
    // ...
  )
}

참조