logo

useTransitionで送信状態を管理する

2025-12-09
a month ago

開発環境

  • react 19.2.0
  • react-dom 19.2.0

前提

  • React 19 以降では、useTransition が 非同期関数を扱えるように拡張されました
  • コンポーネント側で isLoading のような状態を自前で管理せず、外部 API 呼び出しのローディング状態を isPending に任せられます
  • エラーハンドリングは useTransition では扱えないため、別途 try/catch などで行う必要があります

本題

useTransition が非同期関数対応した背景

React 18 までは、startTransition に渡す関数は同期的であることが前提でした。

React 19 では、公式に 非同期関数が許可され、外部 API の呼び出し処理を直接渡せるようになりました。

これにより、以下のメリットがあります。

  • フォーム送信や GET リクエストの処理時に isLoading を自分で宣言しなくて良い
  • transition の中で完結するため、ローディング状態が UI と自然に結びつく
  • 既存の Suspense や並列 UI とも親和性が高い

サンプル実装:GET リクエストのローディング状態管理

以下は JSONPlaceholder API からデータを取得し、そのローディング状態を useTransitionisPending に任せる例です。

import { useState, useTransition } from 'react';

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export default function App() {
  const [post, setPost] = useState(null);
  const [isPending, startTransition] = useTransition();

  const fetchPost = async () => {
    try {
      const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');

      if (!res.ok) throw new Error('API Error');
      const data = await res.json();
      await sleep(1000); // 意図的に時間を長く
      setPost(data);
    } catch (e) {
      console.error(e);
      // エラーハンドリングは各自で実装
    }
  };

  const handleClick = () => {
    startTransition(async () => {
      await fetchPost();
    });
  };

  return (
    <div className="mx-auto max-w-lg space-y-6 p-6">
      <button
        onClick={handleClick}
        disabled={isPending}
        className={`rounded px-4 py-2 font-medium text-white transition ${isPending ? 'cursor-not-allowed bg-gray-400' : 'bg-blue-600 hover:bg-blue-700'} `}
      >
        {isPending ? 'Loading...' : 'Fetch Post'}
      </button>

      {post && (
        <div className="rounded border bg-white p-4 shadow">
          <h3 className="mb-2 text-lg font-semibold">{post.title}</h3>
          <p className="text-gray-700">{post.body}</p>
        </div>
      )}
    </div>
  );
}

実装ポイントまとめ

  • startTransition(async () => { ... }) により非同期処理を素直に書ける
  • isPending がローディング状態を表し、ボタン UI と自然に連動
  • 自前の isLoading が不要になり、状態管理が簡潔化
  • ただし エラー状態は useTransition では扱えないので、各自 try/catch や UI でハンドリングが必要

この API が役立つケース

  • フォーム送信時の「送信中」状態を管理したい
  • GET/POST のローディング表示を自然に行いたい
  • UI をブロックせずに非同期処理を走らせたい

さいごに

useTransition の非同期対応によって、外部 API 通信を伴う UI のローディング制御が非常に簡潔になりました。

状態管理の考慮すべきポイントが減り、コンポーネントが読みやすく保守しやすくなります。

ただし、ローディング状態以外(成功・失敗・例外)の管理は別途必要である点に注意が必要です。

参照