0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React Hooks をHackしよう!【Part22: useEffect 以外でもデータ取得できる?TanStack Query や SWR で賢くデータフェッチングしよう!】

Posted at

React でデータを取得するとき、こんなコードを書いていませんか?

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  let cancelled = false;

  fetch('/api/users')
    .then(res => res.json())
    .then(data => {
      if (!cancelled) setData(data);
    })
    .catch(err => {
      if (!cancelled) setError(err);
    })
    .finally(() => {
      if (!cancelled) setLoading(false);
    });

  return () => { cancelled = true; };
}, []);

毎回このボイラープレートを書くのは辛い... 😫

しかも、このコードにはキャッシュがない重複リクエストを防げない再検証ができないなど、多くの問題があります。

本記事では、useEffect でのデータフェッチングを卒業し、TanStack QuerySWR という専用ライブラリを使って、より効率的で堅牢なデータ取得を実現する方法を解説します!

📝 検証環境: この記事は 2025年12月時点の TanStack Query v5 および SWR v2 を基に書かれています。

💡 サーバー状態とは?
サーバー状態(Server State)は、サーバーに保存され、API を通じて取得・更新するデータのことです。ローカル状態(useState で管理する UI の状態など)とは異なり、キャッシュ、再検証、同期といった特有の課題があります。


1. なぜ useEffect でのデータフェッチングは難しいのか

1.1 典型的な useEffect でのデータフェッチング

まず、useEffect でデータを取得する際の典型的なコードを見てみましょう:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      setLoading(true);
      setError(null);
      try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to fetch');
        const data = await response.json();
        if (!cancelled) {
          setUser(data);
        }
      } catch (e) {
        if (!cancelled) {
          setError(e);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchUser();

    return () => {
      cancelled = true;
    };
  }, [userId]);

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

1.2 useEffect でのデータフェッチングの問題点

このコードには多くの問題があります:

問題 説明
ボイラープレート loading, error, data の状態を毎回定義する必要がある
キャンセル処理 コンポーネントのアンマウント時の処理を手動で実装
キャッシュなし 同じデータを何度もリクエストしてしまう
重複リクエスト 同時に複数のリクエストが発生する可能性
再検証なし データが古くなっても自動更新されない
エラーハンドリング 一貫したエラー処理が難しい
ローディング状態 初回とリフェッチの区別が難しい

1.3 Race Condition(競合状態)の問題

useEffect でのデータフェッチングで特に厄介なのが Race Condition です:

// ❌ Race Condition が発生する可能性
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // userId が 1 → 2 と素早く変わった場合
    // リクエスト1(userId=1)とリクエスト2(userId=2)が同時に飛ぶ
    // リクエスト2が先に完了し、その後リクエスト1が完了すると
    // 画面には userId=1 のデータが表示されてしまう!
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]);

  return <div>{user?.name}</div>;
}

この問題を解決するには、キャンセル処理を適切に実装する必要があります。


2. TanStack Query(React Query)

2.1 TanStack Query とは

TanStack Query(旧 React Query)は、データフェッチライブラリです。API からデータを取得したい時や更新したい時に使うことができます。また、取得したデータはキャッシュされ、キャッシュされたデータを取り出すことも可能です。

💡 名称の変更について
React Query は、v3 までは「React Query」という名称でしたが、v4 系からは「TanStack Query」となっています。

2.2 準備

TanStack Query を使用するための準備をしていきます。

インストール

# npm
npm install @tanstack/react-query

# yarn
yarn add @tanstack/react-query

アプリケーションのセットアップ

TanStack Query を使うための設定を行います。

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

// QueryClient のインスタンスを作成
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // ウィンドウフォーカス時の自動再フェッチを無効化
      refetchOnWindowFocus: false,
    },
  },
});

// アプリケーション全体を QueryClientProvider でラップ
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);

QueryClient はキャッシュを管理するためのクラスです。defaultOptions でデフォルトの動作を設定できます。refetchOnWindowFocus: false を設定すると、ウィンドウにフォーカスが当たった時の自動再フェッチを無効にできます。

2.3 データ取得(useQuery)

データの取得には useQuery フックを使います。

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

// データ取得関数
const fetchTodos = async () => {
  const response = await fetch('/api/todos');
  return response.json();
};

function TodoList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['todos'],      // キャッシュキー(配列)
    queryFn: fetchTodos,      // データ取得関数
  });

  if (isLoading) return <p>読み込み中...</p>;
  if (error) return <p>エラー: {error.message}</p>;

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

useQuery の引数

プロパティ 説明
queryKey キャッシュ管理やデータ再取得に使用されるキー。文字列の配列で渡す
queryFn データを取得するための関数。Promise を返す必要がある

useQuery の返り値

プロパティ 説明
data 取得したデータ
isLoading 初回ローディング中かどうか
isFetching バックグラウンドでフェッチ中かどうか
error エラーオブジェクト
isError エラー状態かどうか
isSuccess 成功状態かどうか
refetch 手動で再フェッチする関数

2.4 データ更新(useMutation)

データの追加・変更・削除には useMutation フックを使います。

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

// データ追加関数
const addTodo = async (newTodo) => {
  const response = await fetch('/api/todos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newTodo),
  });
  return response.json();
};

function AddTodoForm() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: addTodo,
    onSuccess: () => {
      // 成功時にキャッシュを無効化して再フェッチ
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    mutation.mutate({
      title: formData.get('title'),
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="新しいTodo" />
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? '追加中...' : '追加'}
      </button>
    </form>
  );
}

useMutation の引数

プロパティ 説明
mutationFn データを更新するための関数
onSuccess 成功時に実行されるコールバック
onError エラー時に実行されるコールバック
onSettled 成功・エラーに関わらず実行されるコールバック

ミューテーション実行

mutation.mutate(data) を呼び出すことで、mutationFn が実行されます。引数として渡したデータが mutationFn に渡されます。

2.5 キャッシュされたデータを利用する

useQuery を実行したコンポーネントとは別のコンポーネントで、取得したデータを使いたい場合があります。そういった時に、useQueryClient フックと getQueryData メソッドでキャッシュされたデータを取り出すことができます。

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

function TodoStats() {
  const queryClient = useQueryClient();
  
  // キャッシュからデータを取得
  const todos = queryClient.getQueryData(['todos']);

  if (!todos) return <p>データがありません</p>;

  const completedCount = todos.filter((todo) => todo.completed).length;

  return (
    <div>
      <p>総数: {todos.length}</p>
      <p>完了: {completedCount}</p>
      <p>未完了: {todos.length - completedCount}</p>
    </div>
  );
}

getQueryData の使い方

引数 説明
queryKey 取り出したいキャッシュの queryKey(配列)

💡 キャッシュの仕組み
useQueryClient フックは、その時点の QueryClient を取得できます。この中にキャッシュされたデータや defaultOptions の設定情報などが含まれています。getQueryData メソッドに queryKey を渡すことで、対応するキャッシュデータを取得できます。

2.6 実践的な使用例

Todo アプリを例に、データ取得・追加・キャッシュ利用の流れを見てみましょう。

データ取得関数の準備

// api/todos.ts
export const fetchTodos = async () => {
  const response = await fetch('/api/todos');
  if (!response.ok) throw new Error('Failed to fetch');
  return response.json();
};

export const addTodo = async (newTodo) => {
  const response = await fetch('/api/todos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newTodo),
  });
  if (!response.ok) throw new Error('Failed to add');
  return response.json();
};

メインコンポーネント

import { useQuery } from '@tanstack/react-query';
import { fetchTodos } from './api/todos';

function App() {
  const { data: todos, isLoading } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  });

  return (
    <div>
      <h1>Todo アプリ</h1>
      <AddTodoForm />
      {isLoading ? (
        <p>読み込み中...</p>
      ) : (
        <TodoList todos={todos} />
      )}
      <TodoStats />
    </div>
  );
}

追加フォームコンポーネント

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { addTodo } from './api/todos';

function AddTodoForm() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: addTodo,
    onSettled: () => {
      // データ追加後にキャッシュを無効化して最新データを取得
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    mutation.mutate({
      title: formData.get('title'),
      completed: false,
    });
    e.target.reset();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="新しいTodo" required />
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? '追加中...' : '追加'}
      </button>
      {mutation.isError && <p>エラーが発生しました</p>}
    </form>
  );
}

統計表示コンポーネント(キャッシュ利用)

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

function TodoStats() {
  const queryClient = useQueryClient();
  const todos = queryClient.getQueryData(['todos']);

  if (!todos) return null;

  return (
    <p>
      現在のTodo数: {todos.length}</p>
  );
}

2.7 TanStack Query の主な機能まとめ

機能 説明
自動キャッシュ 同じ queryKey のデータは自動的にキャッシュ・共有される
自動再フェッチ ウィンドウフォーカス時、ネットワーク再接続時に自動で再取得
キャッシュの無効化 invalidateQueries でキャッシュを無効化し、再取得をトリガー
楽観的更新 UI を即座に更新し、バックグラウンドで同期
無限スクロール useInfiniteQuery でページネーションをサポート
並列/依存クエリ 複数のクエリを効率的に管理
DevTools 公式 DevTools でキャッシュ状態を可視化

3. SWR

3.1 SWR とは

SWR とは Next.js でおなじみの Vercel 社が提供しているデータ取得のためのライブラリです。React Hooks ベースの API で使うことができ、TanStack Query より軽量でシンプルな API が特徴です。

💡 SWR という名前の由来
"SWR" という名前は、HTTP RFC 5861 で提唱された HTTP キャッシュ無効化戦略である stale-while-revalidate に由来しています。SWR は、まずキャッシュからデータを返し(stale)、次にフェッチリクエストを送り(revalidate)、最後に最新のデータを持ってくるという戦略です。

SWR の特徴

特徴 説明
高速かつ軽量 バンドルサイズが約4KBと非常に小さい
キャッシュ 再利用可能なデータ取得や重複したリクエストを排除できる
SSR/ISR/SSG Next.js との相性が良くサーバーサイドレンダリングをサポート
TypeScript 完全な TypeScript 対応
宣言的 React Hooks で API が提供されているため宣言的にデータ取得を記述できる
Suspense React 推奨ではないが Suspense に対応している

3.2 準備

インストール

# npm
npm install swr

# yarn
yarn add swr

# pnpm
pnpm add swr

3.3 基本的な使い方(useSWR)

SWR でデータを取得する基本的な例を見てみましょう。

import useSWR, { Fetcher } from 'swr';

// レスポンスの型定義
interface User {
  id: number;
  name: string;
  email: string;
}

// fetcher 関数の定義
const fetcher: Fetcher<User[], string> = (url) =>
  fetch(url).then((res) => res.json());

function UserList() {
  const { data, error, isLoading } = useSWR<User[]>(
    '/api/users',
    fetcher
  );

  if (error) return <div>ユーザーの取得に失敗しました</div>;
  if (isLoading) return <div>読み込み中...</div>;

  return (
    <ul>
      {data?.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

useSWR の引数

引数 説明
第1引数(key) API の URL を渡すことが多いが、キャッシュのキーとして扱われる
第2引数(fetcher) 実際に API リクエストを行いデータを取得する関数。Promise を返す関数であれば axios なども使用可能
第3引数(options) オプション設定(省略可能)

💡 fetcher について
公式サイトでは fetch を使っていますが、axios など任意の API クライアントを使用することができます。Fetcher という SWR 提供の型定義を見ると分かりますが、Promise を返す関数であれば渡すことができます。

3.4 key について

デフォルトで SWR はグローバルキャッシュを使用してすべてのコンポーネント間でデータを共有することができます。このデータを区別するために使用するのが key です。

異なるデータリソースやデータを表す key はユニークにする必要があります。基本的には API の URL で問題ない場合が多いですが、以下のように URL 内に含まれないデータ(ここではトークン)によって結果が変わるという場合もあります:

// ❌ トークンが変わっても key は同一のため、トークンが変わる前のデータを返す
useSWR('https://api.github.com/repos/OWNER/REPO/issues', url => 
  fetchWithToken(url, token)
);

この場合は、key を配列とし、token を配列に含めることで urltoken の組み合わせが key となるため、トークンが変更した場合も新しいデータとして保存し、共有することができます:

// ✅ url と token の組み合わせが key となる
useSWR(
  ['https://api.github.com/repos/OWNER/REPO/issues', token], 
  ([url]) => fetchWithToken(url, token)
);

3.5 取得したデータを更新する

自動更新

ページにフォーカスを合わせるかタブを切り替えると SWR が自動的にデータを再検証し、更新します。デフォルトで有効なため、無効化したい場合は revalidateOnFocusfalse にしましょう。

また、オプションの refreshInterval を設定することで、指定した時間ごとに定期的な自動更新を行うこともできます:

const { data, error } = useSWR(
  '/api/notifications',
  fetcher,
  { refreshInterval: 1000 } // 1秒ごとに再フェッチ
);

手動更新(mutate)

手動で更新したい場合は返り値の mutate を使用します:

const { data, error, mutate } = useSWR(
  '/api/users',
  fetcher
);

return (
  <button onClick={() => mutate()}>
    データを更新
  </button>
);

3.6 重複したリクエストを排除する

dedupingInterval オプションを設定することでそのミリ秒の間は再レンダリングや画面のリロードで再リクエストを行おうとしても重複したリクエストとして SWR が判断しリクエストが実行されません。変更頻度が低いデータの取得時に使えそうですね。

const { data, error } = useSWR(
  '/api/users',
  fetcher,
  { dedupingInterval: 60 * 1000 } // 1分間は重複リクエストを排除
);

3.7 エラーハンドリング

API リクエストでエラーが起きた場合は、返り値の error にエラーオブジェクトがセットされます:

const { data, error } = useSWR('/api/users', fetcher);

if (error) return <div>エラーが発生しました: {error.message}</div>;

グローバルエラーハンドリング

Sentry にエラーを送信したいなど共通したエラーハンドリングを行いたい場合、SWRConfig コンテキストですべての SWR フックに設定することができます:

import { SWRConfig } from 'swr';
import * as Sentry from "@sentry/browser";

function App() {
  return (
    <SWRConfig value={{
      onError: (error, key) => {
        if (error.status !== 403 && error.status !== 404) {
          Sentry.captureException(error);
        }
      }
    }}>
      <YourApp />
    </SWRConfig>
  );
}

3.8 Suspense 対応

suspense オプションを有効化することで、React Suspense を使用することができます:

import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import useSWR from 'swr';

function UserList() {
  const { data } = useSWR('/api/users', fetcher, { suspense: true });

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

function App() {
  return (
    <ErrorBoundary fallback={<div>エラーが発生しました</div>}>
      <Suspense fallback={<div>読み込み中...</div>}>
        <UserList />
      </Suspense>
    </ErrorBoundary>
  );
}

3.9 useSWR の API

const { data, error, isLoading, isValidating, mutate } = useSWR(
  key,      // キャッシュキー(通常は URL)
  fetcher,  // データ取得関数
  options   // オプション
);

主なオプション

useSWR('/api/users', fetcher, {
  revalidateOnFocus: true,     // フォーカス時に再検証
  revalidateOnReconnect: true, // 再接続時に再検証
  refreshInterval: 0,          // ポーリング間隔(0で無効)
  dedupingInterval: 2000,      // 重複排除の間隔
  errorRetryCount: 3,          // エラー時のリトライ回数
});

3.10 条件付きフェッチ

// null または undefined を渡すとフェッチをスキップ
function ConditionalFetch({ shouldFetch, userId }) {
  const { data } = useSWR(
    shouldFetch ? `/api/users/${userId}` : null,
    fetcher
  );
  // ...
}

// 関数でキーを返す場合、エラーがスローされるとフェッチをスキップ
function DependentFetch({ userId }) {
  const { data: user } = useSWR(`/api/users/${userId}`, fetcher);
  const { data: projects } = useSWR(
    () => `/api/users/${user.id}/projects`, // user がないとエラー → スキップ
    fetcher
  );
  // ...
}

3.11 GET 以外のリクエスト(useSWRMutation)

SWR はデータ取得を主軸としたライブラリですが、POST リクエストなどデータの作成/更新を行う場合にも対応しています。この場合 useSWR の代わりに useSWRMutation を使います。

import useSWRMutation from 'swr/mutation';

interface Issue {
  title: string;
  body: string;
}

// POST リクエストを行う関数
async function createIssue(url: string, { arg }: { arg: Issue }) {
  const { title, body } = arg;
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ title, body }),
  });
  return response.json();
}

function CreateIssueButton() {
  const { trigger, isMutating } = useSWRMutation(
    '/api/issues',
    createIssue
  );

  return (
    <button
      disabled={isMutating}
      onClick={async () => {
        try {
          await trigger({
            title: '新しい Issue',
            body: 'Issue の内容です',
          });
          alert('Issue を作成しました!');
        } catch (e) {
          console.error(e);
          alert('Issue 作成に失敗しました。');
        }
      }}
    >
      {isMutating ? '作成中...' : 'Issue を作成'}
    </button>
  );
}

useSWRMutation のポイント

項目 説明
第1引数(key) 主にリクエスト先の URL を指定
第2引数 POST/PUT/PATCH/DELETE リクエストを行う関数
trigger 任意のタイミングで実行する関数
isMutating 実行中かどうかを返す boolean(連打防止に便利)
data 実行後に API の結果が必要な場合に使用

💡 PUT や DELETE も同様
useSWRMutation は PUT、PATCH、DELETE の場合も同様に扱うことができます。メソッドを変更するだけで対応可能です。

3.12 グローバル設定

import { SWRConfig } from 'swr';

function App() {
  return (
    <SWRConfig
      value={{
        fetcher: (url) => fetch(url).then(res => res.json()),
        revalidateOnFocus: false,
        errorRetryCount: 2,
      }}
    >
      <YourApp />
    </SWRConfig>
  );
}

// 各コンポーネントでは fetcher を省略可能
function UserProfile({ userId }) {
  const { data } = useSWR(`/api/users/${userId}`);
  // ...
}

3.13 無限スクロール

import useSWRInfinite from 'swr/infinite';

function InfiniteUserList() {
  const getKey = (pageIndex, previousPageData) => {
    if (previousPageData && !previousPageData.length) return null; // 終了
    return `/api/users?page=${pageIndex}`;
  };

  const { data, size, setSize, isValidating } = useSWRInfinite(
    getKey,
    fetcher
  );

  const users = data ? data.flat() : [];
  const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined';

  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
      
      <button
        onClick={() => setSize(size + 1)}
        disabled={isLoadingMore || isValidating}
      >
        {isLoadingMore ? '読み込み中...' : 'もっと見る'}
      </button>
    </div>
  );
}

3.14 SWR の主な機能まとめ

機能 説明
軽量 バンドルサイズが小さい(約4KB)
シンプルな API 最小限の設定で使える
自動再検証 フォーカス時、再接続時に自動で再取得
重複排除 同時に複数の同じリクエストを1つにまとめる
ローカルミューテーション mutate でキャッシュを即座に更新
SSR サポート Next.js との相性が良い
React Suspense Suspense モードをサポート
useSWRMutation GET 以外のリクエスト(POST/PUT/DELETE)に対応

4. useEffect をいつ使い続けるべきか

データフェッチングには専用ライブラリが推奨されますが、useEffect が適している場面もあります。

4.1 useEffect を使うべき場面

ユースケース 理由
イベントリスナーの登録 サブスクリプション型の処理は useEffect が適切
WebSocket 接続 長期的な接続の管理
外部ライブラリの初期化 DOM に依存するライブラリのセットアップ
タイマーの設定 setInterval, setTimeout の管理
DOM 操作 直接的な DOM 操作が必要な場合
// ✅ イベントリスナー → useEffect
useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

// ✅ WebSocket → useEffect
useEffect(() => {
  const ws = new WebSocket('wss://example.com');
  ws.onmessage = (event) => setMessages(prev => [...prev, event.data]);
  return () => ws.close();
}, []);

// ✅ 外部ライブラリ → useEffect
useEffect(() => {
  const chart = new Chart(canvasRef.current, config);
  return () => chart.destroy();
}, []);

4.2 判断フローチャート

5. 実践的なパターン

5.1 認証状態の管理(TanStack Query)

function useAuth() {
  return useQuery({
    queryKey: ['auth'],
    queryFn: () => fetch('/api/auth/me').then(res => res.json()),
    retry: false,
    staleTime: Infinity, // 自動再検証を無効化
  });
}

function useLogin() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: (credentials) =>
      fetch('/api/auth/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
      }).then(res => res.json()),
    onSuccess: (data) => {
      queryClient.setQueryData(['auth'], data.user);
    },
  });
}

function useLogout() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: () => fetch('/api/auth/logout', { method: 'POST' }),
    onSuccess: () => {
      queryClient.setQueryData(['auth'], null);
      queryClient.invalidateQueries(); // 全てのキャッシュをクリア
    },
  });
}

5.2 検索機能(SWR)

import useSWR from 'swr';
import { useDebouncedValue } from '@mantine/hooks'; // または自作

function SearchUsers() {
  const [query, setQuery] = useState('');
  const [debouncedQuery] = useDebouncedValue(query, 300);

  const { data: users, isLoading } = useSWR(
    debouncedQuery ? `/api/users/search?q=${debouncedQuery}` : null,
    fetcher
  );

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="ユーザーを検索..."
      />
      
      {isLoading && <p>検索中...</p>}
      
      {users?.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

5.3 ポーリング(定期的なデータ更新)

// TanStack Query
const { data } = useQuery({
  queryKey: ['notifications'],
  queryFn: fetchNotifications,
  refetchInterval: 30000, // 30秒ごとに再フェッチ
});

// SWR
const { data } = useSWR('/api/notifications', fetcher, {
  refreshInterval: 30000, // 30秒ごとに再フェッチ
});

まとめ

各ツールの使い分け

ユースケース 推奨ツール
API からのデータ取得 TanStack Query / SWR
キャッシュが必要なデータ TanStack Query / SWR
複雑なミューテーション TanStack Query
シンプルな CRUD SWR
Next.js プロジェクト SWR
イベントリスナー useEffect
WebSocket useEffect
タイマー useEffect
外部ライブラリ初期化 useEffect
ツール 一言でまとめると
useEffect 「汎用的だが、データフェッチには向かない」
TanStack Query 「機能豊富で、複雑な要件に対応」
SWR 「軽量でシンプル、サクッと導入」

useEffect でのデータフェッチングからだけでなく、専用ライブラリで快適なデータ取得を実現しましょう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?