본문 바로가기
React & TypeScript

[TIL] TanStack Query란

by 어느새벽 2024. 6. 21.

TanStack Query는 웹 개발에서 데이터를 쉽게 가져오고 서버 상태(서버와의 통신을 통해 가져오는 데이터)를 관리할 수 있게 도와주는 도구(라이브러리)이다. 이전에는 React Query라고 불렸는데 리액트 뿐만 아니라 다른 SPA 프레임워크에서도 사용 가능하기 때문에 이름이 바뀌었다고 한다.

이 도구를 사용하면 웹사이트가 서버에서 데이터를 가져오거나 업데이트할 때 생기는 복잡한 문제를 더 쉽게 해결할 수 있다. 예를 들어, 블로그 글 목록이나 상품 목록을 서버에서 가져오는 작업을 쉽게 해준다.

왜 TanStack Query가 필요할까?

  1. 데이터를 자동으로 관리: TanStack Query는 데이터를 서버에서 가져오거나 업데이트하는 일을 자동으로 관리해준다. 만약 데이터를 가져오다가 에러가 나면, 그걸 어떻게 처리할지 자동으로 정해줄 수 있다.
  2. 간단한 코드: 데이터를 관리하는 코드를 간단하게 쓸 수 있어서, 개발자가 복잡한 코드를 짜지 않아도 된다.
  3. 실시간 업데이트: 서버에서 데이터가 바뀌면 웹사이트도 그걸 자동으로 감지하고 업데이트할 수 있다.

TanStack Query의 주요 기능

  1. 데이터 가져오기(Fetching) : 서버에서 데이터를 가져와서 웹사이트에 보여준다.
  2. 데이터 캐싱(Caching): 한 번 가져온 데이터를 메모리에 저장해 두고, 같은 데이터를 또 가져올 필요 없이 빠르게 사용할 수 있다.
  3. 자동 업데이트(Updating): 서버의 데이터가 바뀌면, 웹사이트의 데이터도 자동으로 바뀌도록 할 수 있다.

TanStack Query 사용법

tanstackQuery 설치하기

npm install @tanstack/react-query

yarn add @tanstack/react-query

 

적용할 범위(ex.전역)에 Provider 이용하여 적용 (App.jsx 또는 main.jsx)에 세팅 권장

// main.jsx

import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);

 

useQuery 기본 사용법

예시로 json-server를 사용한다고 해보자

{
  "todos": [
    {
      "id": "1715926482394",
      "title": "리액트 공부하기",
      "isDone": true
    },
    {
      "id": "1715926492887",
      "title": "Node.js 공부하기",
      "isDone": true
    },
    {
      "id": "1715926495834",
      "title": "영화보기",
      "isDone": false
    }
  ]
}

 

TanStack Query를 사용하기 위해서는 코드에 다음과 같이 추가하면 된다.

import { useQuery } from "@tanstack/react-query";
import axios from "axios";

const App = () => {
  const fetchTodos = async () => {
    const response = await axios.get("http://localhost:4000/todos");
    return response.data;
  };

  const {data: todos, isPending, isError } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  });

  if (isPending) {
    return <div>로딩중입니다...</div>;
  }

  if (isError) {
    return <div>데이터 조회 중 오류가 발생했습니다.</div>;
  }

  return (
    <div>
      <h3>TanStack Query</h3>
      <ul>
        {todos.map((todo) => {
          return (
            <li
              key={todo.id}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "10px",
                backgroundColor: "aliceblue",
              }}
            >
              <h4>{todo.title}</h4>
              <p>{todo.isDone ? "Done" : "Not Done"}</p>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default App;

 

 

useMutation 기본 사용법

useMutation은 데이터를 서버에 보내서 뭔가를 변경할 때 사용하는 도구이다. 예를들어 사용자가 댓글을 작성하거나, 새로 가입하거나, 기존 데이터를 수정할 때 데이터를 서버로 보내는 작업을 처리한다.

 

아래 예제로 살펴보면

 

1. 새로운 댓글 달기

  • 입력 필드에서 사용자가 댓글을 입력한다.
  • "Add Comment" 버튼을 누르면 댓글이 서버로 전송된다.
    • 서버로 전송 중 일 때는 " Sending..." 메세지
    • 전송 성공하면 " Comment added! " 메세지
    • 전송 실패하면 " Error sending comment "ㅑ 메세지를 보여준다.
import React, { useState } from 'react';
import { useMutation } from '@tanstack/react-query';

// 댓글을 서버로 보내는 함수
async function postComment(newComment) {
  const response = await fetch('https://api.example.com/comments', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(newComment),
  });
  return response.json();
}

function CommentForm() {
  const [comment, setComment] = useState('');
  const mutation = useMutation(postComment);

  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ text: comment });
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <textarea 
          value={comment} 
          onChange={(e) => setComment(e.target.value)} 
          placeholder="Write a comment..."
        />
        <button type="submit">Add Comment</button>
      </form>
      {mutation.isLoading && <p>Sending...</p>}
      {mutation.isError && <p>Error sending comment.</p>}
      {mutation.isSuccess && <p>Comment added!</p>}
    </div>
  );
}

export default CommentForm;

 

2. 프로필 정보 수정하기

  1. 입력 필드에 사용자가 이름과 이메일을 입력한다.
  2. " Update Profile "버튼을 누르면 수정된 정보가 서버로 전송된다.
    1. 서버로 전송 중 일 때는 " Sending..." 메세지
    2. 전송 성공하면 " Comment added! " 메세지
    3. 전송 실패하면 " Error sending comment "ㅑ 메세지를 보여준다.
import React, { useState } from 'react';
import { useMutation } from '@tanstack/react-query';

// 프로필 정보를 서버로 보내는 함수
async function updateProfile(updatedData) {
  const response = await fetch('https://api.example.com/profile', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(updatedData),
  });
  return response.json();
}

function ProfileForm() {
  const [profile, setProfile] = useState({ name: '', email: '' });
  const mutation = useMutation(updateProfile);

  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate(profile);
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Name"
          value={profile.name}
          onChange={(e) => setProfile({ ...profile, name: e.target.value })}
        />
        <input
          type="email"
          placeholder="Email"
          value={profile.email}
          onChange={(e) => setProfile({ ...profile, email: e.target.value })}
        />
        <button type="submit">Update Profile</button>
      </form>
      {mutation.isLoading && <p>Updating...</p>}
      {mutation.isError && <p>Error updating profile.</p>}
      {mutation.isSuccess && <p>Profile updated!</p>}
    </div>
  );
}

export default ProfileForm;

 

invalidateQueries란 무엇일까?

invalidateQueries는 사용자가 데이터를 변경한 후에 그 데이터를 다시 가져오도록 하는 기능이다.

예를 들어, 사용자가 새로운 댓글을 달거나 프로필을 수정하면, 이미 화면에 보여지고 있는 데이터가 최신 상태가 아닐 수도 있다. 이때 invalidateQueries를 사용하면 최신 데이터를 다시 가져와서 화면을 업데이트한다.

이 기능을 사용하면 사용자에게 항상 최신 상태의 데이터를 보여줄 수 있어서 훨씬 편리하고 정확하다.

 

invalidateQueries 기본 사용법

1. 새로운 댓글 달기 (invalidateQueries 포함)

 

  1. 댓글 가져오기: fetchComments 함수가 서버에서 댓글 목록을 가져온다.
  2. 댓글 추가하기: 사용자가 새로운 댓글을 작성하고 버튼을 누르면 postComment 함수가 서버로 새로운 댓글을 보낸다.
  3. invalidateQueries 사용: 새로운 댓글을 성공적으로 보내면, onSuccess 콜백 함수가 호출되고, queryClient.invalidateQueries(['comments'])를 사용해서 기존의 댓글 목록을 무효화하고 다시 가져온다. 이렇게 하면 화면에 최신 댓글 목록이 보여지게 된다.

 

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

// 서버에서 댓글 목록을 가져오는 함수
async function fetchComments() {
  const response = await fetch('https://api.example.com/comments');
  return response.json();
}

// 새로운 댓글을 서버로 보내는 함수
async function postComment(newComment) {
  const response = await fetch('https://api.example.com/comments', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(newComment),
  });
  return response.json();
}

function Comments() {
  const queryClient = useQueryClient();
  const { data: comments, isLoading, isError } = useQuery(['comments'], fetchComments);

  const mutation = useMutation(postComment, {
    onSuccess: () => {
      // 댓글 추가가 성공하면 댓글 목록을 무효화하고 다시 가져옴
      queryClient.invalidateQueries(['comments']);
    },
  });

  const [newComment, setNewComment] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ text: newComment });
    setNewComment('');
  };

  if (isLoading) return <p>Loading comments...</p>;
  if (isError) return <p>Error loading comments.</p>;

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <textarea
          value={newComment}
          onChange={(e) => setNewComment(e.target.value)}
          placeholder="Write a comment..."
        />
        <button type="submit">Add Comment</button>
      </form>
      {mutation.isLoading && <p>Sending...</p>}
      {mutation.isError && <p>Error sending comment.</p>}
      {mutation.isSuccess && <p>Comment added!</p>}
      <ul>
        {comments.map((comment, index) => (
          <li key={index}>{comment.text}</li>
        ))}
      </ul>
    </div>
  );
}

export default Comments;

 

2. 프로필 정보 수정하기 (invalidateQueries 포함)

 

  1. 프로필 정보 가져오기: fetchProfile 함수가 서버에서 현재 프로필 정보를 가져온다.
  2. 프로필 정보 수정하기: 사용자가 이름과 이메일을 수정하고 버튼을 누르면 updateProfile 함수가 서버로 수정된 정보를 보낸다.
  3. invalidateQueries 사용: 프로필 정보가 성공적으로 수정되면, onSuccess 콜백 함수가 호출되고, queryClient.invalidateQueries(['profile'])를 사용해서 기존의 프로필 정보를 무효화하고 다시 가져온다. 이렇게 하면 화면에 최신 프로필 정보가 보여지게 된다.

 

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

// 서버에서 프로필 정보를 가져오는 함수
async function fetchProfile() {
  const response = await fetch('https://api.example.com/profile');
  return response.json();
}

// 프로필 정보를 서버로 보내는 함수
async function updateProfile(updatedData) {
  const response = await fetch('https://api.example.com/profile', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(updatedData),
  });
  return response.json();
}

function ProfileForm() {
  const queryClient = useQueryClient();
  const { data: profile, isLoading, isError } = useQuery(['profile'], fetchProfile);

  const mutation = useMutation(updateProfile, {
    onSuccess: () => {
      // 프로필 정보가 업데이트되면 다시 가져옴
      queryClient.invalidateQueries(['profile']);
    },
  });

  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ name, email });
  };

  if (isLoading) return <p>Loading profile...</p>;
  if (isError) return <p>Error loading profile.</p>;

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <button type="submit">Update Profile</button>
      </form>
      {mutation.isLoading && <p>Updating...</p>}
      {mutation.isError && <p>Error updating profile.</p>}
      {mutation.isSuccess && <p>Profile updated!</p>}
      <div>
        <h2>Current Profile</h2>
        <p>Name: {profile?.name}</p>
        <p>Email: {profile?.email}</p>
      </div>
    </div>
  );
}

export default ProfileForm;