본문 바로가기
SPA/React

[React] React Query의 useMutation에 대해 알아보기

by J4J 2022. 3. 9.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 React Query의 useMutation에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

들어가기에 앞서

 

react query의 기능 업데이트가 주기적으로 이루어졌고 최근 v5로 넘어오면서 여러 가지 변경점들이 발생되었습니다.

 

최근 변경점에 대해서도 확인하고 싶으신 분들은 아래 글들을 참고 부탁드립니다.

 

[React] react-query v5 변경점 알아보기 (1) - 공통 기능 변경

[React] react-query v5 변경점 알아보기 (5) - useMutation 기능 변경

 

 

 

useMutation란?

 

useMutation은 React Query를 이용해 서버에 데이터 변경 작업을 요청할 때 사용합니다.

 ※ 데이터 조회를 할 때는 useQuery를 사용합니다.

 

데이터베이스로 비유하자면 insert, update, delete가 모두 포함됩니다.

 

 

 

useMutation을 코드로 작성하기 위해서는 다음의 개념을 알고 있어야 합니다.

 

  • mutationFn

 

 

 

또한 다음과 같은 형태로 사용됩니다.

 

// 1
const savePerson = useMutation(mutationFn);

// 2
const savePerson = useMutation({
    mutationFn: mutationFn
})

 

 

반응형

 

 

mutationFn란?

 

mutationFn은 mutation Function으로 promise 처리가 이루어지는 함수입니다.

 

다른 말로는 axios를 이용해 서버에 API를 요청하는 부분이라고 생각할 수 있습니다.

 

그렇기 때문에 일반적으로 useMutation을 사용할 때는 다음과 같이 코드가 작성되고는 합니다.

 

// 1
const savePerson = useMutation((person: Iperson) => axios.post('/savePerson', person));

// 2
const savePerson = useMutation({
    mutationFn: (person: Iperson) => axios.post('/savePerson', person)
})

 

 

 

 

mutate

 

mutate는 useMutation을 이용해 작성한 내용들이 실행될 수 있도록 도와주는 trigger 역할을 합니다.

 

즉, useMutation을 정의 해둔 뒤 이벤트가 발생되었을 때 mutate를 사용해주면 되는 겁니다.

 

예시를 들기 위해 데이터를 저장하는 코드를 간단하게 작성하면 다음과 같습니다.

 

import * as React from 'react';
import { useMutation } from 'react-query';
import styled from 'styled-components';
import axios from 'axios';

interface Iperson {
    id: number;
    name: string;
    phone: string;
    age: number;
}

const Mutation = (): JSX.Element => {

    const [person, setPerson] = React.useState<Iperson>({
        id: 0,
        name: '',
        phone: '',
        age: 0,
    })

    const savePerson = useMutation((person: Iperson) => axios.post('http://localhost:8080/savePerson', person)); // useMutate 정의

    const onSavePerson = () => {
        savePerson.mutate(person); // 데이터 저장
    }

    return (
        <Wrapper>
            <Person.InputTable>
                <tbody>
                    <tr>
                        <td>
                            <Person.Text>이름</Person.Text>
                        </td>
                        <td>
                            <input type='text' onChange={(e) => setPerson((curPerson) => {
                                return {
                                    ...curPerson,
                                    name: e.target.value
                                }
                            })}/>
                        </td>
                    </tr>

                    <tr>
                        <td>
                            <Person.Text>전화번호</Person.Text>
                        </td>
                        <td>
                            <input type='text' onChange={(e) => setPerson((curPerson) => {
                                return {
                                    ...curPerson,
                                    phone: e.target.value
                                }
                            })}/>
                        </td>
                    </tr>

                    <tr>
                        <td>
                            <Person.Text>나이</Person.Text>
                        </td>
                        <td>
                            <input type='number' onChange={(e) => setPerson((curPerson) => {
                                return {
                                    ...curPerson,
                                    age: Number(e.target.value)
                                }
                            })}/>
                        </td>
                    </tr>
                </tbody>
            </Person.InputTable>

            <Person.SaveButton onClick={onSavePerson}>저장</Person.SaveButton>
        </Wrapper>
    )
}

export default Mutation;

const Wrapper = styled.div`
    max-width: 728px;

    margin: 0 auto;

    text-align: center;
`;

const Person = {
    InputTable: styled.table`
        border-spacing: 18px 0;

        text-align: center;

        margin: 0 auto;
    `,

    Text: styled.h3``,

    SaveButton: styled.button`
        width: 92px;
        height: 32px;
        
        border: none;
        border-radius: 8px;

        background-color: orange;

        color: #fff;

        cursor: pointer;
    `
}

 

 

 

 

onSuccess, onError, onSettled

 

일반적으로 서버에 데이터 변경 요청을 하게 되면 변경 요청이 성공할 경우에 추가적인 액션을 할 수 있도록 코드를 작성하고는 합니다.

 

이런 상황은 useMutation을 사용할 때도 동일하게 적용이 됩니다.

 

 

 

async/await을 사용할 때는 보통 다음과 같이 결괏값이 있는지를 확인한 뒤 추가 작업을 수행할 수 있는 코드를 작성합니다.

 

try {
    const res = await axios.post('http://localhost:8080/savePerson', person);

    if(res) {
        console.log('success');
    }
} catch(error) {
    console.log('error');
} finally {
    console.log('finally');
}

 

 

 

하지만 useMutation을 사용하면 다음과 같이 좀더 세련되게(?) 표현해줄 수 있습니다.

 

// 1
const savePerson = useMutation((person: Iperson) => axios.post('http://localhost:8080/savePerson', person), {
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
});

// 2
const savePerson = useMutation({
    mutationFn: (person: Iperson) => axios.post('/savePerson', person),
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
})

 

 

 

onSuccess는 요청이 성공되었을 때 실행되는 구간입니다.

 

onError는 에러가 발생될 경우 실행되는 구간입니다.

 

onSettled는 finally 구문처럼 요청이 성공하든 에러가 발생되든 상관없이 마지막에 실행되는 구간입니다.

 

 

 

추가적으로 onSuccess, onError, onSettled는 useMutation을 정의할 때만 사용할 수 있는 것이 아니라 mutate에서도 사용이 가능합니다.

 

const onSavePerson = () => {
    savePerson.mutate(person, {
        onSuccess: () => { // 요청이 성공한 경우
            console.log('onSuccess');
        },
        onError: (error) => { // 요청에 에러가 발생된 경우
            console.log('onError');
        },
        onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
            console.log('onSettled');
        }
    }); // 데이터 저장
}

 

 

 

 

invalidateQueries

 

invalidateQueries는 useQuery에서 사용되는 queryKey의 유효성을 제거해주는 목적으로 사용됩니다.

 

그리고 queryKey의 유효성을 제거해주는 이유는 서버로부터 다시 데이터를 조회해오기 위함입니다.

 

 

 

[React] React Query의 useQuery에 대해 알아보기에서 사용하던 코드와 해당 글에서 사용하던 코드를 합치면 다음과 같은 페이지를 확인할 수 있습니다.

 

코드 실행 결과

 

 

 

일반적인 생각으로는 이름, 전화번호, 나이를 적은 뒤 저장 버튼을 누르게 되면 리스트가 아래에 바로 표현되는 것을 생각할 수 있습니다.

 

하지만 useQuery에는 staleTime과 cacheTime이라는 개념이 존재합니다.

 

정해진 시간이 도달하지 않으면 새로운 데이터가 적재되었더라도 useQuery는 변동 없이 동일한 데이터를 화면에 보여줄 것입니다.

 

결국 사용자 입장에서는 데이터 생성이 제대로 되었는지에 대한 파악이 힘들기 때문에 혼란을 겪을 수 있게 됩니다.

 

 

 

해당 상황을 해결해줄 수 있는 것이 바로 invalidateQueries입니다.

 

데이터를 저장할 때 invalidateQueries를 이용해 useQuery가 가지고 있던 queryKey의 유효성을 제거해주면 캐싱되어있는 데이터를 화면에 보여주지 않고 서버에 새롭게 데이터를 요청하게 됩니다.

 

결국 데이터가 새롭게 추가되었을 때 다시 서버에서 데이터를 가져오게 되면서 추가한 데이터까지 화면에서 확인할 수 있게 됩니다.

 

 

 

사용 방법은 우선 hook을 이용해 등록했던 queryClient를 가져와야 합니다.

 

그리고 원하는 장소에서 사용해주면 되는데, 일반적으로 요청이 성공했을 때 queryKey의 유효성을 제거할것이기 때문에 다음과 같이 코드를 작성해줄 수 있습니다.

 

const queryClient = useQueryClient();  // 등록된 quieryClient 가져오기

const savePerson = useMutation((person: Iperson) => axios.post('http://localhost:8080/savePerson', person), {
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
        queryClient.invalidateQueries('persons'); // queryKey 유효성 제거
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
}); // useMutate 정의

 

 

 

참고적으로 여기서 invalidateQueries안에 'persons'라는 값이 들어가있습니다.

 

그 이유는 위에 링크를 걸어둔 useQuery 글에서 useQuery를 사용할 때 'persons'라는 queryKey를 가지고 person 데이터를 화면에 뿌려주고 있기 때문입니다.

 

 

 

 

setQueryData

 

invalidateQueries를 사용하지 않고도 데이터를 업데이트해줄 수 있는 방법은 있습니다.

 

setQueryData를 활용하면 되는데 setQueryData는 기존에 queryKey에 매핑되어 있는 데이터를 새롭게 정의해줍니다.

 

setQueryData는 다음과 같이 사용해줄 수 있습니다.

 

const queryClient = useQueryClient();  // 등록된 quieryClient 가져오기

const savePerson = useMutation((person: Iperson) => axios.post('http://localhost:8080/savePerson', person), {
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
        queryClient.setQueryData('persons', (data) => {
            const curPersons = data as AxiosResponse<any, any>; // persons의 현재 데이터 확인
            curPersons.data.push(person); // 데이터 push

            return curPersons; // 변경된 데이터로 set
        })
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
}); // useMutate 정의

 

 

 

코드를 이렇게 작성해주면 서버에 다시 persons 데이터를 요청하지 않고도 사용자 화면에 변경된 데이터를 함께 보여줄 수 있습니다.

 

하지만 개인적으로는 invalidateQueries를 사용할 것 같습니다 ㅎㅎ..

 

 

 

 

 

 

 

 

이상으로 React Query의 useMutation에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글