redux
, recoil
, mobx
, zustand
와 같은 라이브러리들이 클라이언트 상태 관리 라이브러리라면 React Query
는 서버 상태 관리 라이브러리, 즉 API를 호출해 서버에서 비동기로 가져오는 데이터들을 관리하기 쉽게 만들어주는 라이브러리다.
클라이언트 상태 관리 라이브러리나 React
의 useState
를 사용해 서버 데이터를 관리하면 비동기 처리 등의 로직이 번거롭고 복잡하다. 간단한 예시로 서버에서 데이터를 받아오기 전까지 로딩 컴포넌트를 띄워주는 코드를 작성해보겠다.
// without React Query
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
setIsLoading(true);
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
setData(data);
} catch (error) {
setError(error);
}
setIsLoading(false);
}
fetchData();
}, []);
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// with React Query
import { useQuery } from 'react-query';
function MyComponent() {
const { data, isLoading, error } = useQuery('users', () => fetch('https://api.example.com/users'));
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
코드가 훨씬 간결하고 직관적으로 변한 모습이다.
이 외에도 캐싱, 메모리 성능 최적화 등 많은 장점이 있다.
이 글에선 React Query에서 가장 자주 사용되는 두 가지 기능을 간략하게 소개할 것이다.
데이터를 READ 할 때 사용한다.
import { useQuery } from 'react-query';
function MyComponent() {
const { data, isLoading, error } = useQuery('users', () => fetch('https://api.example.com/users'));
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
상단의 예시에서 useQuery
는 'users'
string을 첫 번째 인자로, () => fecth()
함수를 두 번째 인자로 받는다.
첫 번째 인자는 queryKey
라는 값인데, query마다 유니크한 값을 지정해 React Query로 하여금 query의 상태와 결과 등을 추적할 수 있게 해준다.
두 번째 인자는 Promise를 반환하는 비동기 함수인 queryFn
인데, query가 데이터를 요청할 때 사용하게 될 함수이다.
만약 첫 번째 인자인 queryKey
에 string이 아니라 배열을 넘겨주게 된다면
(e.g. ['users', userId]
)
배열의 첫 번째 요소는 queryKey
가 되고 그 뒤 요소들은 queryFn
의 인자로 쓸 수 있게 된다.
import { useQuery } from 'react-query';
function MyComponent({ userId }) {
const { data, isLoading, error } = useQuery(['user', userId], () => fetch(`https://api.example.com/users/${userId}`));
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return <p>{data.name}</p>;
}
useQuery
의 결과값을 { data, isLoading, error }
에 저장하고 있다.
data
는 말 그대로 useQuery
를 통해 얻은 데이터이다.
isLoading
은 query가 아직 데이터를 받고 있는 중인지를 나타내는 boolean 값이다.
error
또한 말 그대로 쿼리에서 발생한 에러이다.
가장 간단하게 쓰이는 건 이 정도지만 이 외에도 공식 문서 (https://react-query-v3.tanstack.com/reference/useQuery) 를 보면 아주 다양한 useQuery
의 옵션과 리턴값들을 볼 수 있다.
데이터를 CREATE, UPDATE, DELETE 할 때 사용한다.
아래는 간단한 회원가입 form 예시이다.
import { useMutation } from 'react-query';
function MyComponent() {
const { mutate, isLoading, error } = useMutation(async (newUser) => {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
body: JSON.stringify(newUser),
headers: {
'Content-Type': 'application/json'
}
});
return response.json();
});
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const newUser = {
name: formData.get('name'),
email: formData.get('email')
};
await mutate(newUser);
// do something
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Name:</label>
<input type="text" name="name" />
<br />
<label htmlFor="email">Email:</label>
<input type="email" name="email" />
<br />
<button type="submit" disabled={isLoading}>
Create user
</button>
{error && <p>Error: {error.message}</p>}
</form>
);
}
useMutation
은 Promise를 반환하는 비동기 함수 mutationFn
을 인수로 받고, { mutate, isLoading, error... }
등을 반환한다.
useQuery
를 호출하면 queryFn
이 실행되어 data
를 받아오지만,
useMutation
을 호출하면 mutate
라는 함수를 만들어서 반환해주고, 그 후 다른 곳에서 mutate
함수가 호출돼야 비로소 mutateFn
이 실행된다.
마찬가지로 공식 문서 (https://react-query-v3.tanstack.com/reference/useMutation) 에 더 많은 옵션과 리턴값을 볼 수 있다.
React Query
를 사용해 서버 데이터를 간결하고 간편하게 관리할 수 있다.- READ할 땐
useQuery
를 사용한다.- CREATE, UPDATE, DELETE할 땐
useMutation
을 사용한다.