React - React Query and 적용

김명원·2025년 1월 15일
0

learnReact

목록 보기
23/26

React Query 란?

React Query 소개


React Query는 서버 상태 관리를 위한 강력한 라이브러리로, 데이터 fetching, 캐싱, 동기화, 업데이트 등의 작업을 간편하게 수행할 수 있도록 도와줍니다. 복잡한 서버 데이터 로직을 효과적으로 관리하며, 다양한 API 호출 및 상태 관리 작업을 간소화하여 개발자의 생산성을 높여줍니다.

React Query의 주요 기능


React Query는 다양한 기능을 제공하여 서버 상태 관리를 효율적으로 처리할 수 있습니다. 주요 기능은 다음과 같습니다:

  • 데이터 Fetching: useQuery 훅을 사용하여 데이터를 쉽게 가져올 수 있습니다.
  • 데이터 Mutations: useMutation 훅을 사용하여 데이터 생성, 업데이트, 삭제 등의 작업을 수행할 수 있습니다.
  • 자동 리페칭: 데이터가 변경될 때마다 자동으로 재요청하여 최신 상태를 유지합니다.
  • 캐싱: 캐싱을 통해 불필요한 네트워크 요청을 줄이고 성능을 향상시킵니다.
  • 에러 핸들링: 에러 처리를 위한 간단한 메커니즘을 제공합니다.

설치


React Query를 프로젝트에 설치하고 설정하는 방법을 알아보겠습니다. 공식 문서인 Quick Start | TanStack Query React Docs를 참고하였습니다.

@tanstack/react-query 설치

@tanstack/react-query는 React 애플리케이션에서 서버 상태 관리를 쉽게 할 수 있도록 도와주는 라이브러리입니다. 다음 명령어를 사용하여 설치할 수 있습니다.

npm install @tanstack/react-query

React Query Provider 설정

패키지를 설치한 후, 애플리케이션의 루트 파일(예: index.js 또는 App.js)에 QueryClientProvider를 설정해야 합니다. 이를 통해 React Query의 모든 기능을 애플리케이션에서 사용할 수 있게 됩니다.

import React from 'react';
import ReactDOM from 'react-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';

// QueryClient 생성
const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

설명:

  • QueryClient는 React Query의 핵심 인스턴스로, 캐싱과 데이터 관리의 중심 역할을 합니다.
  • QueryClientProvider를 통해 queryClient를 애플리케이션에 제공하여, 모든 컴포넌트에서 React Query를 사용할 수 있도록 합니다.

주요 기능


React Query에서 useQueryuseMutation은 각각 데이터 fetching과 데이터 변이(mutation)를 관리하기 위한 핵심 훅입니다. 이 두 훅의 스펙과 주요 기능을 상세히 살펴보겠습니다.

useQuery

useQuery | TanStack Query React Docs

useQuery는 서버에서 데이터를 가져오고, 그 데이터를 캐시하여 컴포넌트에서 쉽게 사용할 수 있도록 도와주는 훅입니다. 데이터 fetching 로직을 간결하게 작성할 수 있으며, 자동 리페칭과 캐싱을 통해 애플리케이션의 성능을 최적화할 수 있습니다.

스펙 (Parameters)

const { data, error, isLoading, isFetching, refetch, ... } = useQuery({
  queryKey,   // 쿼리 키
  queryFn,    // 쿼리 함수
  ...options  // 선택적 옵션들
});
  • 예시

    const { data, error, isLoading, isFetching, refetch } = useQuery({
      queryKey: ['todos', userId],
      queryFn: () => axios.get(`/api/todos/${userId}`).then(res => res.data),
      staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 오래된 상태로 간주하지 않음
      cacheTime: 1000 * 60 * 10, // 10분 동안 캐시 유지
      enabled: true, // 쿼리 자동 실행 여부
      refetchOnWindowFocus: false, // 창 포커스 시 리페칭 비활성화
      onSuccess: (data) => console.log('Data fetched successfully', data),
      onError: (error) => console.error('Error fetching data', error),
    });
  • queryKey (필수)

    • 쿼리의 고유한 식별자입니다. 배열이나 문자열로 정의할 수 있으며, 쿼리를 식별하고 캐시에서 데이터를 관리하는 데 사용됩니다.
    • 예: ['todos', userId] 또는 'todos'
  • queryFn (필수)

    • 서버에서 데이터를 가져오는 비동기 함수입니다.
    • 예: () => axios.get('/api/todos')
  • options (선택)

    • 쿼리의 동작을 제어하는 다양한 옵션을 설정할 수 있습니다.
      • staleTime: 데이터가 오래된 상태로 간주되기 전까지의 시간(ms).
      • cacheTime: 데이터가 캐시에서 유지되는 시간(ms).
      • enabled: 쿼리의 자동 실행 여부를 결정합니다.
      • refetchOnWindowFocus: 창이 다시 포커스될 때 데이터를 다시 가져올지 여부.
      • onSuccess: 쿼리가 성공했을 때 호출되는 콜백 함수.
      • onError: 쿼리가 실패했을 때 호출되는 콜백 함수.

리턴 값

  • data: 서버에서 가져온 데이터.
  • error: 쿼리 실행 중 발생한 에러 객체.
  • isLoading: 쿼리의 데이터가 로딩 중인지 여부.
  • isFetching: 백그라운드에서 데이터가 다시 가져오는 중인지 여부.
  • refetch: 데이터를 다시 가져오는 함수.

useMutation

useMutation | TanStack Query React Docs

useMutation은 데이터를 생성, 수정, 삭제하는 등의 변이(mutation) 작업을 관리하기 위한 훅입니다. 서버에 데이터를 변경하는 요청을 보낼 때 유용하게 사용할 수 있으며, 변이 작업의 성공과 실패를 쉽게 처리할 수 있습니다.

스펙 (Parameters)

const { mutate, data, error, isLoading, isSuccess, isError, reset, ... } = useMutation({
  mutationFn,  // 변이 함수
  ...options   // 선택적 옵션들
});
  • 예시

    const { mutate, data, error, isLoading, isSuccess, isError, reset } = useMutation({
      mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
      onSuccess: (data) => {
        console.log('Todo created successfully', data);
        // 필요시 데이터를 refetch하는 등의 로직을 여기에 추가
      },
      onError: (error) => {
        console.error('Error creating todo', error);
      },
      onMutate: (newTodo) => {
        // 낙관적 업데이트를 위한 로직을 여기에 추가
        console.log('Mutate started', newTodo);
      },
      retry: 3, // 실패 시 3번 재시도
      retryDelay: 1000, // 1초 후 재시도
    });
  • mutationFn (필수)

    • 서버에서 변이 작업을 수행하는 비동기 함수입니다. 예를 들어, POST 요청을 통해 새로운 데이터를 생성하는 작업을 할 수 있습니다.
    • 예: (newTodo) => axios.post('/api/todos', newTodo)
  • options (선택)

    • 변이 작업의 동작을 제어하는 다양한 옵션을 설정할 수 있습니다.
      • onSuccess: 변이 작업이 성공했을 때 호출되는 콜백 함수.
      • onError: 변이 작업이 실패했을 때 호출되는 콜백 함수.
      • onMutate: 변이 작업이 시작되기 전에 호출되는 콜백 함수. 낙관적 업데이트에 사용됩니다.
      • retry: 실패 시 변이 작업을 다시 시도하는 횟수.
      • retryDelay: 재시도 간의 시간 간격.

리턴 값

  • mutate: 변이 작업을 실행하는 함수. 이 함수에 인수를 전달하여 변이를 실행합니다.
  • data: 변이 작업의 결과로 반환된 데이터.
  • error: 변이 작업 중 발생한 에러 객체.
  • isLoading: 변이 작업이 진행 중인지 여부.
  • isSuccess: 변이 작업이 성공적으로 완료되었는지 여부.
  • isError: 변이 작업이 실패했는지 여부.
  • reset: 변이 작업의 상태를 초기화하는 함수.

이 두 훅을 사용함으로써, React 애플리케이션에서 서버 상태를 효율적으로 관리하고, 복잡한 데이터 fetching 및 변이 로직을 간단하게 처리할 수 있습니다.

실습 🛠️


실제 프로젝트에서 React Query를 어떻게 활용할 수 있는지 실습을 통해 알아보겠습니다.

src/pages/About.jsx

아래는 About.jsx 파일에서 React Query를 사용하여 데이터를 fetching하고, 변이를 수행하는 예제입니다.

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
import Button from '../components/Button';

function About() {
  const queryClient = useQueryClient();
  
  // useQuery를 사용하여 데이터 fetching
  const { isLoading, error, data } = useQuery({
    queryKey: ['canvases'],
    queryFn: () =>
      axios.get('http://localhost:8000/canvases/').then(res => res.data),
    initialData: [],
  });

  // useMutation을 사용하여 데이터 생성
  const { mutate: createNewCanvas, isLoading: isLoadingCreate } = useMutation({
    mutationFn: newCanvas =>
      axios.post('http://localhost:8000/canvases/', newCanvas),
    onSuccess: () => {
      // 데이터가 성공적으로 생성되면 'canvases' 쿼리를 무효화하여 데이터를 다시 fetching
      queryClient.invalidateQueries(['canvases']);
    },
  });

  // 버튼 클릭 시 새로운 캔버스 생성
  const handleCreate = () => {
    createNewCanvas({ title: 'new canvas' });
  };

  return (
    <div>
      <h2 className="text-3xl">useQuery</h2>
      {isLoading && <p>...Loading</p>}
      {error && <p className="text-red-700">{error.message}</p>}
      {data.map(item => (
        <li key={item.id}>{item.title}</li>
      ))}

      <h2 className="text-3xl">useMutation</h2>
      {isLoadingCreate && <p>...Loading</p>}
      <Button onClick={handleCreate}>등록</Button>
    </div>
  );
}

export default About;

설명:

  • useQuery 사용:

    • queryKey: ['canvases']로 설정하여 이 쿼리를 고유하게 식별합니다.
    • queryFn: API 엔드포인트에서 데이터를 fetching하는 함수입니다.
    • initialData: 초기 데이터를 빈 배열로 설정하여, 데이터가 로드되기 전에도 빈 리스트를 렌더링합니다.
    • isLoading: 데이터가 로딩 중인지 여부를 나타냅니다.
    • error: 에러가 발생했을 때 에러 메시지를 표시합니다.
    • data: fetching된 데이터를 리스트 형태로 출력합니다.
  • useMutation 사용:

    • mutationFn: 새로운 캔버스를 생성하기 위해 API에 POST 요청을 보내는 함수입니다.
    • onSuccess: 변이 작업이 성공하면 'canvases' 쿼리를 무효화하여 최신 데이터를 다시 fetching하도록 합니다.
    • mutate: createNewCanvas로 이름을 변경하여, 새로운 캔버스 생성을 트리거하는 함수입니다.
    • isLoadingCreate: 변이 작업이 진행 중인지 여부를 나타냅니다.
    • handleCreate: 버튼 클릭 시 createNewCanvas를 호출하여 새로운 캔버스를 생성합니다.

참고 📚


결론 ✨


이번 포스트에서는 React Query의 소개와 주요 기능, 설치 방법, 핵심 훅인 useQueryuseMutation의 스펙 및 사용법, 그리고 실습 예제를 통해 React 애플리케이션에서 서버 상태를 효율적으로 관리하는 방법에 대해 알아보았습니다. React Query를 활용함으로써 데이터 fetching과 변이 작업을 간편하게 처리하고, 애플리케이션의 성능과 사용자 경험을 향상시킬 수 있습니다.

React Query 프로젝트 적용하기 🚀

React 애플리케이션에서 서버 상태 관리를 효율적으로 처리하기 위해 React Query를 어떻게 적용할 수 있는지 알아보겠습니다. 이번 포스트에서는 React Query의 기본 사용법부터 실제 프로젝트에 적용하는 방법까지 단계별로 설명합니다.

기본 사용법 🛠️

React Query를 프로젝트에 적용하기 위해 먼저 기본적인 사용법을 숙지해야 합니다. 주요 기능인 useQueryuseMutation을 활용하여 데이터 fetching과 mutations을 관리하는 방법을 소개합니다.

useQuery를 사용한 데이터 Fetching 📄

useQuery는 서버에서 데이터를 가져오고, 그 데이터를 캐시하여 컴포넌트에서 쉽게 사용할 수 있도록 도와주는 훅입니다. 데이터 fetching 로직을 간결하게 작성할 수 있으며, 자동 리페칭과 캐싱을 통해 애플리케이션의 성능을 최적화할 수 있습니다.

예시:

import { useQuery } from 'react-query';
import axios from 'axios';

function ExampleComponent() {
  const { data, error, isLoading } = useQuery({
    queryKey: 'todos', // 쿼리 키
    queryFn: () => axios.get('/api/todos').then(res => res.data) // 쿼리 함수
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

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

export default ExampleComponent;

설명:

  • queryKey: 쿼리를 고유하게 식별하는 키입니다. 동일한 queryKey를 사용하는 쿼리는 동일한 캐시를 공유하게 됩니다.
  • queryFn: 데이터를 fetching하는 비동기 함수입니다. 이 예제에서는 Axios를 사용하여 /api/todos 엔드포인트에서 데이터를 가져옵니다.
  • 상태 관리: isLoadingerror를 통해 데이터 로딩 상태와 에러를 관리합니다.

useMutation을 사용한 데이터 Mutations 🔄

useMutation은 데이터를 생성, 수정, 삭제하는 등의 변이(mutation) 작업을 관리하기 위한 훅입니다. 서버에 데이터를 변경하는 요청을 보낼 때 유용하게 사용할 수 있으며, 변이 작업의 성공과 실패를 쉽게 처리할 수 있습니다.

예시:

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

function AddTodo() {
  const queryClient = useQueryClient();
  
  const mutation = useMutation({
    mutationFn: newTodo => axios.post('/api/todos', newTodo), // 변이 함수
    onSuccess: () => {
      // 데이터가 성공적으로 추가되면 'todos' 쿼리를 무효화하여 최신 상태로 갱신
      queryClient.invalidateQueries('todos');
    },
  });

  const handleAdd = () => {
    mutation.mutate({ title: 'New Todo' }); // 새로운 Todo 추가
  };

  return <button onClick={handleAdd}>Add Todo</button>;
}

export default AddTodo;

설명:

  • mutationFn: 변이 작업을 수행하는 함수입니다. 이 예제에서는 새로운 Todo를 생성하기 위해 POST 요청을 보냅니다.
  • onSuccess: 변이 작업이 성공했을 때 호출되는 콜백 함수로, invalidateQueries를 사용하여 관련 쿼리를 무효화하고 데이터를 다시 fetching합니다.
  • mutate: 변이 작업을 실행하는 함수로, 새로운 Todo 데이터를 인자로 전달합니다.

자동 리페칭 (Auto Refetching) 🔄

React Query는 데이터를 주기적으로 자동으로 다시 fetching하여 최신 상태를 유지할 수 있는 기능을 제공합니다. refetchInterval 옵션을 사용하여 특정 간격마다 데이터를 재요청할 수 있습니다.

예시:

import { useQuery } from 'react-query';
import axios from 'axios';

function AutoRefetchingExample() {
  const { data, error, isLoading } = useQuery({
    queryKey: 'todos', // 쿼리 키
    queryFn: () => axios.get('/api/todos').then(res => res.data), // 쿼리 함수
    refetchInterval: 5000, // 5초마다 자동으로 리페칭
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

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

export default AutoRefetchingExample;

설명:

  • refetchInterval: 데이터가 자동으로 다시 fetching되는 간격을 밀리초 단위로 설정합니다. 이 예제에서는 5초마다 데이터를 다시 요청합니다.
  • 실시간 데이터 업데이트: 실시간으로 변경되는 데이터를 주기적으로 업데이트할 때 유용합니다.

캐싱 (Caching) 💾

React Query는 데이터를 캐시하여 불필요한 네트워크 요청을 줄이고, 애플리케이션의 성능을 향상시킵니다. staleTimecacheTime 옵션을 사용하여 캐시의 동작을 제어할 수 있습니다.

예시:

import { useQuery } from 'react-query';
import axios from 'axios';

function CachingExample() {
  const { data, error, isLoading } = useQuery({
    queryKey: 'todos', // 쿼리 키
    queryFn: () => axios.get('/api/todos').then(res => res.data), // 쿼리 함수
    staleTime: 10000, // 10초 동안 캐시 데이터가 신선하게 유지됨
    cacheTime: 300000, // 5분 동안 캐시된 데이터를 유지
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

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

export default CachingExample;

설명:

  • staleTime: 데이터가 신선하게 유지되는 시간을 설정합니다. 이 시간 동안은 데이터를 다시 fetching하지 않고 캐시된 데이터를 사용합니다.
  • cacheTime: 데이터가 캐시에 유지되는 시간을 설정합니다. 이 시간이 지나면 캐시가 삭제됩니다.
  • 효율적인 데이터 관리: 자주 변경되지 않는 데이터에 대해 불필요한 요청을 줄여줍니다.

에러 핸들링 (Error Handling) ❗

React Query는 에러 발생 시 이를 효과적으로 처리할 수 있는 메커니즘을 제공합니다. onError 콜백과 retry 옵션을 활용하여 에러 상황을 관리할 수 있습니다.

예시:

import { useQuery } from 'react-query';
import axios from 'axios';

function ErrorHandlingExample() {
  const { data, error, isLoading, refetch } = useQuery({
    queryKey: 'todos',
    queryFn: () => axios.get('/api/todos').then(res => res.data),
    retry: 1, // 요청이 실패하면 1번 더 시도
    onError: (err) => {
      console.error('데이터를 가져오는 데 실패했습니다:', err.message);
    }
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return (
    <div>
      <p>Error: {error.message}</p>
      <button onClick={() => refetch()}>Retry</button>
    </div>
  );

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

export default ErrorHandlingExample;

설명:

  • retry: 요청이 실패했을 때 자동으로 재시도하는 횟수를 설정합니다. 이 예제에서는 1번 재시도합니다.
  • onError: 에러 발생 시 호출되는 콜백 함수로, 에러 메시지를 콘솔에 출력합니다.
  • 사용자 피드백: 에러가 발생했을 때 사용자에게 에러 메시지를 표시하고, 재시도할 수 있는 버튼을 제공합니다.

프로젝트 적용 🏗️

React Query의 기본 사용법을 익혔다면, 이제 실제 프로젝트에 적용해보겠습니다. 기존 코드를 리팩토링하여 React Query를 도입하는 과정을 단계별로 설명합니다.

기본 사용법

useQuery를 사용한 데이터 Fetching 📄

먼저, useQuery를 사용하여 데이터를 fetching하는 기본적인 예제를 살펴보겠습니다.

import { useQuery } from 'react-query';
import axios from 'axios';

function ExampleComponent() {
  const { data, error, isLoading } = useQuery({
    queryKey: 'todos', // 쿼리 키
    queryFn: () => axios.get('/api/todos').then(res => res.data) // 쿼리 함수
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

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

export default ExampleComponent;

useMutation을 사용한 데이터 Mutations 🔄

다음으로, useMutation을 사용하여 데이터를 생성하는 예제를 살펴보겠습니다.

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

function AddTodo() {
  const queryClient = useQueryClient();
  
  const mutation = useMutation({
    mutationFn: newTodo => axios.post('/api/todos', newTodo), // 변이 함수
    onSuccess: () => {
      // 데이터가 성공적으로 추가되면 'todos' 쿼리를 무효화하여 최신 상태로 갱신
      queryClient.invalidateQueries('todos');
    },
  });

  const handleAdd = () => {
    mutation.mutate({ title: 'New Todo' }); // 새로운 Todo 추가
  };

  return <button onClick={handleAdd}>Add Todo</button>;
}

export default AddTodo;

자동 리페칭 (Auto Refetching) 🔄

React Query의 자동 리페칭 기능을 활용하여 데이터를 주기적으로 업데이트하는 방법을 알아보겠습니다.

import { useQuery } from 'react-query';
import axios from 'axios';

function AutoRefetchingExample() {
  const { data, error, isLoading } = useQuery({
    queryKey: 'todos', // 쿼리 키
    queryFn: () => axios.get('/api/todos').then(res => res.data), // 쿼리 함수
    refetchInterval: 5000, // 5초마다 자동으로 리페칭
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

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

export default AutoRefetchingExample;

설명:

  • refetchInterval: 데이터가 자동으로 다시 fetching되는 간격을 밀리초 단위로 설정합니다. 이 예제에서는 5초마다 데이터를 다시 요청합니다.
  • 실시간 데이터 업데이트: 실시간으로 변경되는 데이터를 주기적으로 업데이트할 때 유용합니다.

캐싱 (Caching) 💾

React Query의 캐싱 기능을 활용하여 애플리케이션의 성능을 향상시키는 방법을 알아보겠습니다.

import { useQuery } from 'react-query';
import axios from 'axios';

function CachingExample() {
  const { data, error, isLoading } = useQuery({
    queryKey: 'todos', // 쿼리 키
    queryFn: () => axios.get('/api/todos').then(res => res.data), // 쿼리 함수
    staleTime: 10000, // 10초 동안 캐시 데이터가 신선하게 유지됨
    cacheTime: 300000, // 5분 동안 캐시된 데이터를 유지
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

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

export default CachingExample;

설명:

  • staleTime: 데이터가 신선하게 유지되는 시간을 설정합니다. 이 시간 동안은 데이터를 다시 fetching하지 않고 캐시된 데이터를 사용합니다.
  • cacheTime: 데이터가 캐시에 유지되는 시간을 설정합니다. 이 시간이 지나면 캐시가 삭제됩니다.
  • 효율적인 데이터 관리: 자주 변경되지 않는 데이터에 대해 불필요한 요청을 줄여줍니다.

에러 핸들링 (Error Handling) ❗

React Query의 에러 핸들링 기능을 활용하여 에러 발생 시 사용자에게 적절한 피드백을 제공하는 방법을 알아보겠습니다.

import { useQuery } from 'react-query';
import axios from 'axios';

function ErrorHandlingExample() {
  const { data, error, isLoading, refetch } = useQuery({
    queryKey: 'todos',
    queryFn: () => axios.get('/api/todos').then(res => res.data),
    retry: 1, // 요청이 실패하면 1번 더 시도
    onError: (err) => {
      console.error('데이터를 가져오는 데 실패했습니다:', err.message);
    }
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return (
    <div>
      <p>Error: {error.message}</p>
      <button onClick={() => refetch()}>Retry</button>
    </div>
  );

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

export default ErrorHandlingExample;

설명:

  • retry: 요청이 실패했을 때 자동으로 재시도하는 횟수를 설정합니다. 이 예제에서는 1번 재시도합니다.
  • onError: 에러 발생 시 호출되는 콜백 함수로, 에러 메시지를 콘솔에 출력합니다.
  • 사용자 피드백: 에러가 발생했을 때 사용자에게 에러 메시지를 표시하고, 재시도할 수 있는 버튼을 제공합니다.

프로젝트 적용 🏗️

React Query의 기본 사용법을 익혔다면, 이제 실제 프로젝트에 적용해보겠습니다. 기존 코드를 리팩토링하여 React Query를 도입하는 과정을 단계별로 설명합니다.

실습 - 기존 코드 리팩토링 🛠️

기존의 useApiRequest 훅을 사용한 코드 예제를 React Query를 사용하여 리팩토링해보겠습니다.

canvas.js 수정 📁

먼저, API 요청을 관리하는 canvas.js 파일을 수정합니다.

import { canvases } from './http';
import { v4 as uuidv4 } from 'uuid';
import dayjs from 'dayjs';

export async function getCanvases(params) {
  const payload = Object.assign(
    {
      _sort: 'lastModified',
      _order: 'desc',
    },
    params,
  );
  const { data } = await canvases.get('/', { params: payload });
  return data;
}

export function createCanvas() {
  const newCanvas = {
    title: uuidv4().substring(0, 4) + '_새로운 린 캔버스',
    lastModified: dayjs().format('YYYY-MM-DD HH:mm:ss'),
    category: '신규',
  };
  return canvases.post('/', newCanvas);
}

export async function deleteCanvas(id) {
  await canvases.delete(`/${id}`);
}

export async function getCanvasById(id) {
  const { data } = await canvases.get(`/${id}`);
  return data;
}

// json-server
export async function updateTitle(id, title) {
  /**
   * post - 새로운 자원 생성
   * put - 기존 자원 전체 업데이트 또는 새 자원 생성
   * patch - 일부 수정
   */
  await canvases.patch(`/${id}`, { title });
}

export async function updateCanvas(id, canvas) {
  await canvases.put(`/${id}`, canvas);
}

설명:

  • getCanvases: 특정 파라미터를 받아 캔버스 데이터를 fetching합니다.
  • createCanvas: 새로운 캔버스를 생성하는 함수입니다.
  • deleteCanvas: 특정 캔버스를 삭제하는 함수입니다.
  • getCanvasById: 특정 ID를 가진 캔버스를 fetching하는 함수입니다.
  • updateTitle & updateCanvas: 캔버스의 제목을 업데이트하거나 전체 캔버스를 업데이트하는 함수입니다.

리팩토링 코드 🛠️

이제 react-query를 사용하여 Home.jsx 파일을 리팩토링해보겠습니다.

  • src/pages/Home.jsx
import { useState } from 'react';
import { createCanvas, deleteCanvas, getCanvases } from '../api/canvas';

import CanvasList from '../components/CanvasList';
import SearchBar from '../components/SearchBar';
import ViewToggle from '../components/ViewToggle';
import Loading from '../components/Loading';
import Error from '../components/Error';
import Button from '../components/Button';

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

function Home() {
  const [searchText, setSearchText] = useState('');
  const [isGridView, setIsGridView] = useState(true);

  const queryClient = useQueryClient();

  // 1] 데이터 조회 - useQuery 사용
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['canvases', searchText],
    queryFn: () => getCanvases({ title_like: searchText }),
    initialData: [],
  });

  // 2] 데이터 생성 - useMutation 사용
  const { mutate: createNewCanvas, isLoading: isLoadingCreate } = useMutation({
    mutationFn: createCanvas,
    onSuccess: () => queryClient.invalidateQueries(['canvases']),
    onError: err => alert(err.message),
  });

  // 3] 데이터 삭제 - useMutation 사용
  const { mutate: deleteCanvasMutation } = useMutation({
    mutationFn: deleteCanvas,
    onSuccess: () => queryClient.invalidateQueries(['canvases']),
    onError: err => alert(err.message),
  });

  const handleDeleteItem = async id => {
    // if (confirm('삭제 하시겠습니까?') === false) {
    //   return;
    // }
    deleteCanvasMutation(id);
  };

  const handleCreateCanvas = async () => {
    createNewCanvas();
  };

  return (
    <>
      <div className="mb-6 flex flex-col sm:flex-row items-center justify-between">
        <SearchBar searchText={searchText} setSearchText={setSearchText} />
        <ViewToggle isGridView={isGridView} setIsGridView={setIsGridView} />
      </div>
      <div className="flex justify-end mb-6">
        <Button onClick={handleCreateCanvas} loading={isLoadingCreate}>
          등록하기
        </Button>
      </div>
      {isLoading && <Loading />}
      {error && <Error message={error.message} onRetry={refetch} />}
      {!isLoading && !error && (
        <CanvasList
          filteredData={data}
          isGridView={isGridView}
          searchText={searchText}
          onDeleteItem={handleDeleteItem}
        />
      )}
    </>
  );
}

export default Home;

설명:

  1. 데이터 조회 (useQuery):

    • queryKey: ['canvases', searchText]로 설정하여 검색 텍스트에 따라 쿼리를 고유하게 식별합니다.
    • queryFn: getCanvases 함수를 호출하여 데이터를 fetching합니다.
    • initialData: 초기 데이터를 빈 배열로 설정하여, 데이터 로딩 전에도 빈 리스트를 렌더링할 수 있습니다.
  2. 데이터 생성 (useMutation):

    • mutationFn: createCanvas 함수를 사용하여 새로운 캔버스를 생성합니다.
    • onSuccess: 변이 작업이 성공하면 invalidateQueries를 호출하여 ['canvases'] 쿼리를 무효화하고 데이터를 다시 fetching합니다.
    • onError: 변이 작업이 실패하면 에러 메시지를 알림으로 표시합니다.
  3. 데이터 삭제 (useMutation):

    • mutationFn: deleteCanvas 함수를 사용하여 특정 캔버스를 삭제합니다.
    • onSuccess: 변이 작업이 성공하면 invalidateQueries를 호출하여 ['canvases'] 쿼리를 무효화하고 데이터를 다시 fetching합니다.
    • onError: 변이 작업이 실패하면 에러 메시지를 알림으로 표시합니다.
  4. 핸들러 함수:

    • handleDeleteItem: 특정 캔버스를 삭제하는 핸들러 함수로, deleteCanvasMutation을 호출하여 삭제 작업을 수행합니다.
    • handleCreateCanvas: 새로운 캔버스를 생성하는 핸들러 함수로, createNewCanvas를 호출하여 생성 작업을 수행합니다.
  5. 컴포넌트 렌더링:

    • 검색 및 뷰 토글: SearchBarViewToggle 컴포넌트를 사용하여 검색 텍스트와 뷰 방식을 관리합니다.
    • 등록 버튼: 등록하기 버튼을 클릭하면 새로운 캔버스를 생성합니다.
    • 로딩 및 에러 상태: isLoadingerror 상태에 따라 로딩 스피너나 에러 메시지를 표시합니다.
    • 캔버스 리스트: 데이터가 로드되고 에러가 없을 경우 CanvasList 컴포넌트를 통해 캔버스를 표시합니다.

마무리 🏁

React Query를 사용하면 데이터 fetching과 mutations을 더욱 효율적으로 관리할 수 있습니다. 이번 포스트에서는 기본적인 사용법부터 실제 프로젝트에 적용하는 방법까지 살펴보았습니다.

참고 📚

profile
개발자가 되고 싶은 정치학도생의 기술 블로그

0개의 댓글