🐎
TanStack Queryで条件付きフェッチ
ユーザーの入力に応じてデータを取得したいときなど、条件付きで useQuery
や useSuspenseQuery
を使いたい場面があります。この記事ではディスカッション上で紹介されていた方法をまとめます。
useSuspenseQueryを使う場合
useSuspenseQuery
を使う場合は2通りの方法があります。
コンポーネントを分割する
Suspenseではデータ取得が成功していることが期待されるため、条件によるレンダリングやエラーハンドリングは親コンポーネントで行います。
TodoList.tsx
"use client";
import { useSuspenseQuery } from "@tanstack/react-query";
import { Suspense, useState } from "react";
type Todo = {
id: number;
userId: number;
title: string;
completed: boolean;
};
// データ取得用の関数
const getTodos = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const todos = (await res.json()) as Todo[];
return todos;
};
/** 条件によるレンダリングを担う親コンポーネント **/
const TodoList = () => {
const [isChecked, setIsChecked] = useState(false);
const handleCheckboxClick = () => {
setIsChecked((prev) => !prev);
};
return (
<div>
<label className="flex gap-x-2">
<span className=" after:content-[':']">show todo list</span>
<input
type="checkbox"
checked={isChecked}
onClick={handleCheckboxClick}
/>
</label>
{isChecked && (
<Suspense fallback={<div>Loading...</div>}>
<Component />
</Suspense>
)}
</div>
);
};
/** 子コンポーネントではTodoの取得が成功していることが約束される **/
const Component = () => {
const { data: todos } = useSuspenseQuery({
queryKey: ["todos"],
queryFn: getTodos,
});
return (
todos.length > 0 && (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
);
};
export { TodoList };
queryFnでnullを返す
条件を満たすまでの間、queryFnでnullを返すという方法もあります。この場合は todos
の型がnullになり得るので、条件式の追加が必要になります。
@@ -36,20 +36,21 @@ const TodoList = () => {
</label>
{isChecked && (
<Suspense fallback={<div>Loading...</div>}>
- <Component />
+ <Component shouldShowTodos={isChecked} />
</Suspense>
)}
</div>
);
};
-const Component = () => {
+const Component = ({ shouldShowTodos }: { shouldShowTodos: boolean }) => {
const { data: todos } = useSuspenseQuery({
queryKey: ["todos"],
- queryFn: getTodos,
+ queryFn: () => (shouldShowTodos ? getTodos() : null),
});
return (
+ todos &&
^^^^ 型が Todo[] | nullになるため、ハンドリングが必要
todos.length > 0 && (
<ul>
{todos.map((todo) => (
useQueryを使う場合
useQuery
を使う場合は enabled
オプションで関数の実行を制御できます。
ただ、Suspenseが利用できる場合は積極的に useSuspenseQuery
を活用した方が良さそうです。
"use client";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
type Todo = {
id: number;
userId: number;
title: string;
completed: boolean;
};
const getTodos = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const todos = (await res.json()) as Todo[];
return todos;
};
export const TodoList = () => {
const [isChecked, setIsChecked] = useState(false);
const { data: todos, isLoading } = useQuery({
queryKey: ["todos"],
queryFn: getTodos,
enabled: isChecked,
});
const handleCheckboxClick = () => {
setIsChecked((prev) => !prev);
};
return (
<div>
<label className="flex gap-x-2">
<span className=" after:content-[':']">show todo list</span>
<input
type="checkbox"
checked={isChecked}
onClick={handleCheckboxClick}
/>
</label>
{todos && todos.length > 0 && (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)}
{isLoading && <div>Loading...</div>}
</div>
);
};
おわりに
今回はTanStack Queryを使って条件付きフェッチを実装する方法を紹介しました。Suspenseは便利な機能ですが、コンポーネントを適切に分割することが求められます。Reactの考え方に沿ったコンポーネント設計がより重要になっていると感じました。
Discussion