리액트

TanStack Query (React Query) 공식문서 정복하기 (2)

배트리버 2024. 6. 11. 15:10

오늘은 지난 포스트에서 설명했듯 useQuery와 useMutation에 대해 작성해 볼 것이다.

 

Queries

쿼리는 고유 키와 연결된 비동기 데이터 소스에 대한 선언적 의존이다. 쿼리는 서버에서 데이터를 가져오기 위해 Promise 기반 메서드(예: GET 및 POST)를 사용한다. 데이터를 수정하는 메서드에는 Mutations를 사용하는 것이 좋다.

 

서버에서 데이터를 가져올 때 -> useQuery 
데이터를 수정할 때 -> useMutaion 

 

useQuery 사용법 

queryKey : 쿼리의 고유 키 , 캐시를 관리하기 위한 키 값으로 배열 형태로 사용 

queryFn : promise 반환하는 함수 

 

import { useQuery } from '@tanstack/react-query'

function App() {
  const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}

 

제공된 고유 키는 내부적으로 재패칭, 캐싱, 쿼리 공유 등에 사용된다. 

useQuery가 반환하는 쿼리 결과 객체는 쿼리에 필요한 모든 정보를 포함한다. (데이터, 에러 , isLoading, isFetching  ... ) 

 

우리가 흔히 데이터를 요청할 때 데이터를 요청하고 기다린 후 데이터가 성공적으로 요청되거나 , 에러가 발생하거나 하는 상태가 존재한다. useQuery를 사용하면 이러한 상태들을 관리하는데 용이하다. 

 

useQuery 결과 상태 

  1. isPending 또는 status === 'pending' - 아직 데이터가 없음
  2. isError 또는 status === 'error' - 오류 발생
  3. isSuccess 또는 status === 'success' - 성공적으로 데이터가 있음

또한 각 상태에 따라 추가 정보를 얻을 수 있다. 

  1. isFetching - 어떤 상태에서도 쿼리가 패칭 중일 때
  2. error - isError 상태일 때 오류 정보
  3. data - isSuccess 상태일 때 데이터
function Todos() {
  const { isPending, isError, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
  });

  if (isPending) {
    return <span>Loading...</span>;
  }

  if (isError) {
    return <span>Error: {error.message}</span>;
  }

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

 

function Todos() {
  const { status, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
  });

  if (status === 'pending') {
    return <span>Loading...</span>;
  }

  if (status === 'error') {
    return <span>Error: {error.message}</span>;
  }

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

 

이런식으로 사용하게 되면 데이터의 상태에 따라 완전히 데이터를 받아오기 전 사용자에게 로딩창을 띄워주거나 에러 발생 시 에러메세지를 띄워줄 수 있다. 

 

또한 추가적으로 status 필드 외에  fetchStatus 속성도 있다.

  • fetchStatus === 'fetching' - 현재 패칭 중
  • fetchStatus === 'paused' - 패칭이 필요하지만 중단됨 (네트워크 모드 가이드 참조)
  • fetchStatus === 'idle' - 현재 아무 것도 하지 않음

왜 두 가지 상태가 필요한가?

백그라운드 재패칭과 stale-while-revalidate 로직은 모든 상태와 fetchStatus 조합을 가능하게 한다.

예를 들어

  • 성공 상태의 쿼리는 보통 idle fetchStatus에 있지만, 백그라운드 재패칭 중이라면 fetching일 수도 있다.
  • 데이터가 없는 쿼리는 보통 pending status와 fetching fetchStatus에 있지만, 네트워크 연결이 없으면 paused일 수도 있다.

따라서 쿼리는 실제로 데이터를 패칭하지 않고도 pending 상태일 수 있다. 

  • status는 데이터에 대한 정보를 제공한다 ->  데이터가 있는지 여부
  • fetchStatus는 queryFn에 대한 정보를 제공한다 -> 실행 중인지 여부

 


Mutations 

queries와 달리 mutations는 데이터를 생성/업데이트/삭제하거나 서버 측 효과를 수행하는 데 사용된다. (CUD에 해당하는 기능)

mutations는 쿼리키는 필요하지 않고 queryFn만 사용한다. 

import { useMutation } from '@tanstack/react-query';
import axios from 'axios';

function App() {
  const mutation = useMutation({
    mutationFn: (newTodo) => {
      return axios.post('/todos', newTodo);
    },
  });

  return (
    <div>
      {mutation.isPending ? (
        'Adding todo...'
      ) : (
        <>
          {mutation.isError ? (
            <div>An error occurred: {mutation.error.message}</div>
          ) : null}

          {mutation.isSuccess ? <div>Todo added!</div> : null}

          <button
            onClick={() => {
              mutation.mutate({ id: new Date(), title: 'Do Laundry' });
            }}
          >
            Create Todo
          </button>
        </>
      )}
    </div>
  );
}

 

useMutaion 결과 상태

  1. isIdle 또는 status === 'idle' - 현재 대기 중이거나 초기 상태
  2. isPending 또는 status === 'pending' - 현재 실행 중
  3. isError 또는 status === 'error' - 오류 발생
  4. isSuccess 또는 status === 'success' - 성공적으로 완료되어 데이터가 있음

상태에 따른 추가 정보:

  1. error - 오류 상태일 때 오류 정보 제공
  2. data - 성공 상태일 때 데이터 제공

useMutaion 사용법 

기본적인 사용방법은 위에서 설명했고 아래는 특별히 주의할 점에 대해 작성해볼 것이다. 

 

1. mutate 함수는 비동기 함수이므로 React 16 및 이전 버전 의 이벤트 콜백에서 직접 사용할 수 없다 .onSubmit 에서 이벤트에 액세스해야 하는 경우 mutate를 다른 함수로 래핑해야 합니다 . 이는 React 이벤트 풀링 때문

// This will not work in React 16 and earlier
const CreateTodo = () => {
  const mutation = useMutation({
    mutationFn: (event) => {
      event.preventDefault()
      return fetch('/api', new FormData(event.target))
    },
  })

  return <form onSubmit={mutation.mutate}>...</form>
}

// This will work
const CreateTodo = () => {
  const mutation = useMutation({
    mutationFn: (formData) => {
      return fetch('/api', formData)
    },
  })
  const onSubmit = (event) => {
    event.preventDefault()
    mutation.mutate(new FormData(event.target))
  }

  return <form onSubmit={onSubmit}>...</form>
}

 

2. Mutation 상태 재설정 

요청의 오류나 데이터를 지워야 할 때는 reset 함수를 사용하면 된다. 

const CreateTodo = () => {
  const [title, setTitle] = useState('')
  const mutation = useMutation({ mutationFn: createTodo })

  const onCreateTodo = (e) => {
    e.preventDefault()
    mutation.mutate({ title })
  }

  return (
    <form onSubmit={onCreateTodo}>
      {mutation.error && (
        <h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
      )}
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <br />
      <button type="submit">Create Todo</button>
    </form>
  )
}

 

3. Mutation 재시도 

TanStack Query는 mutation 오류 시 재시도하지 않지만 retry 옵션으로 가능하게 할 수 있다. 

const mutation = useMutation({
  mutationFn: addTodo,
  retry: 3,
});

retry : 3 을 설정해둔단면 동작 실패 시 3번 재시도 한다는 의미이다. 

 

4. Promise 반환 

성공 시 해결되거나 오류가 발생하는 promise을 얻으려면 mutate  대신 mutateAsync 사용하면 된다.  

const mutation = useMutation({ mutationFn: addTodo })

try {
  const todo = await mutation.mutateAsync(todo)
  console.log(todo)
} catch (error) {
  console.error(error)
} finally {
  console.log('done')
}

 

 

오늘은 이렇게 useQuery와 useMutation의 차이점과 사용방법에 대해 알아보았다. 

각 훅들이 제공하는 유용한 옵션들도 있으니 함께 알아보고 사용하면 좋을 것 같다 !! 

https://tanstack.com/query/latest/docs/framework/react/reference/useQuery

 

useQuery | TanStack Query React Docs

Does this replace [Redux, MobX, etc]? react

tanstack.com

https://tanstack.com/query/latest/docs/framework/react/reference/useMutation

 

useMutation | TanStack Query React Docs

Does this replace [Redux, MobX, etc]? react

tanstack.com

 

 

다음 포스트에서는 진행중인 프로젝트에 리액트 쿼리를 적용한 결과에 대해 작성해 볼 것이다.