きっかけ
UseStateがいっぱいあって読みにくい・・・と思ったときに、
出会ったライブラリです。
npm install @tanstack/react-query
でインストール。
公式はここ。
https://tanstack.com/query/latest
Next.js(App Router)でTanStack Queryを使う理由 〜useEffectとの違いを一覧画面を例に解説〜
はじめに
ReactでAPIからデータを取得する場合、多くの方が最初に思いつくのはuseEffectとuseStateを組み合わせる方法ではないでしょうか。
例えば一覧画面では、次のようなコードを書くことがよくあります。
const [rows, setRows] = useState<Row[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let mounted = true;
async function loadData() {
try {
setLoading(true);
setError(null);
const data = await getRows();
if (mounted) {
setRows(data);
}
} catch (err) {
if (mounted) {
setError(
err instanceof Error
? err.message
: "データの取得に失敗しました"
);
}
} finally {
if (mounted) {
setLoading(false);
}
}
}
loadData();
return () => {
mounted = false;
};
}, []);
この実装でも問題なく動作します。
しかし、画面数やAPIが増えるにつれて、同じようなコードを何度も書くことになります。
そこで活躍するのがTanStack Queryです。
TanStack Queryとは?
TanStack Queryは、サーバーから取得したデータ(Server State)を管理するためのライブラリです。
Reactには
- useState
- useEffect
- useMemo
- useContext
などのフックはありますが、
「APIから取得したデータを効率よく管理する仕組み」
は標準では用意されていません。
そのため、多くのReactプロジェクトでは
- TanStack Query
- SWR
などのライブラリが利用されています。
useEffectでは何をしているのか
先ほどのコードを整理すると、実は4つの仕事をしています。
① データを保持する
const [rows, setRows] = useState([]);
APIから取得した一覧データを保存しています。
② ローディング状態を保持する
const [loading, setLoading] = useState(true);
通信中かどうかを管理しています。
③ エラー状態を保持する
const [error, setError] = useState(null);
エラーが発生した場合のメッセージを管理しています。
④ APIを呼び出す
useEffect(() => {
loadData();
}, []);
画面表示時にAPIを実行しています。
つまり、
API実行
↓
loading = true
↓
成功なら rows に保存
失敗なら error に保存
↓
loading = false
という処理をすべて自分で実装しています。
TanStack Queryならどうなる?
TanStack Queryでは、
const {
data: rows = [],
isLoading,
error,
} = useQuery({
queryKey: ["rows"],
queryFn: getRows,
});
これだけです。
「コードが短すぎて何をしているのか分からない」と感じるかもしれません。
実は内部では、
- APIの呼び出し
- ローディング状態の管理
- エラー状態の管理
- データの保持
をライブラリ側が行っています。
つまり、自分で書いていたuseStateやuseEffectの多くをTanStack Queryが肩代わりしてくれるということです。
data: rows = [] の意味
この書き方はJavaScriptの分割代入です。
const {
data: rows = [],
} = useQuery(...);
これは、
-
dataという名前で返ってきた値を -
rowsという変数名で受け取り -
dataがundefinedなら空配列を使う
という意味になります。
つまり、
const rows = query.data ?? [];
とほぼ同じです。
useQueryは何を返している?
慣れないうちは、
const query = useQuery({
queryKey: ["rows"],
queryFn: getRows,
});
と書く方が理解しやすいでしょう。
例えば、
query.data
query.isLoading
query.error
query.refetch
などを利用できます。
慣れてきたら分割代入で書くのがおすすめです。
TanStack Queryを導入するメリット
ローディング管理
自分で
setLoading(true);
setLoading(false);
を書く必要がありません。
エラー管理
setError(...)
を書く必要もありません。
キャッシュ
例えば、
一覧画面
↓
詳細画面
↓
一覧画面へ戻る
という画面遷移では、毎回APIを呼び直すのではなく、キャッシュされたデータを利用できます。
これにより、画面表示が高速になります。
再取得
一覧を更新したい場合は、
refetch();
だけです。
更新後の再取得
例えば編集画面で保存したあとに一覧画面を更新したい場合は、
queryClient.invalidateQueries({
queryKey: ["rows"],
});
とするだけで、一覧データを再取得できます。
React 19ではmountedフラグは不要?
React 18以降では、アンマウント後のsetStateに対する警告は削除されました。
そのため、以前ほどmountedフラグを書く機会は多くありません。
通信を途中で中止したい場合は、AbortControllerを利用する方法が推奨されています。
また、TanStack Queryを利用すると、リクエスト管理もライブラリ側に任せられるため、自前で状態管理を書く場面がさらに減ります。
SWRとの違い
React・Next.jsでは、TanStack QueryのほかにSWRもよく利用されています。
| 項目 | SWR | TanStack Query |
|---|---|---|
| データ取得 | ◎ | ◎ |
| キャッシュ | ◎ | ◎ |
| データ更新(Mutation) | ○ | ◎ |
| キャッシュの無効化 | ○ | ◎ |
| ページネーション | ○ | ◎ |
| 学習コスト | 低い | やや高い |
SWRは「データ取得」に特化したシンプルなライブラリです。
一方、TanStack Queryは「サーバーデータ全体を管理する」ことを目的としています。
どちらを選ぶべき?
以下のようなアプリではSWRでも十分でしょう。
- ブログ
- ニュースサイト
- お知らせ一覧
一方で、
- 管理画面
- 業務システム
- CRUD(登録・更新・削除)が多いアプリ
- 多数のAPIを扱うアプリ
では、TanStack Queryの恩恵を受けやすくなります。
まとめ
TanStack Queryは単なる「API取得ライブラリ」ではありません。
サーバーから取得したデータを効率よく管理するためのライブラリです。
useEffectとuseStateだけでも実装できますが、画面数やAPIが増えるにつれて、同じようなコードが繰り返し登場します。
TanStack Queryを導入すると、
- データ取得
- ローディング管理
- エラー管理
- キャッシュ
- 再取得
といった処理を共通化でき、コードの重複を大きく減らせます。
最初はuseQueryの書き方に戸惑うかもしれませんが、「今まで自分で書いていた状態管理をライブラリが代わりに行ってくれる」と考えると、理解しやすくなるでしょう。