useTransition 完全入門【React】UIをブロックしない状態更新

ReactのuseTransitionとは何か、優先度の低い状態更新をバックグラウンドで処理する仕組みを初心者向けに解説。isPendingを使ったローディング表示の実装例も紹介します。

#react#hooks#usetransition#javascript#frontend

検索ボックスに文字を入力したとき、画面が固まって入力がもたつく——そんな経験をしたことはありませんか?

重いリストのフィルタリングや大量のデータ描画が原因で、UI が応答しなくなることがあります。useTransition を使うと、緊急でない処理をバックグラウンドに回し、UIの応答性を維持できます。

この記事でわかること:

  • useTransition が必要な場面
  • isPendingstartTransition の使い方
  • タブ切り替えや検索フィルタリングでの実装例
  • 使えない場面(入力フィールドのstate)

useTransition とは?

useTransition は、状態更新の優先度を下げて、UIをブロックせずに処理するフックです。

const [isPending, startTransition] = useTransition();
戻り値説明
isPendingトランジション処理が進行中かどうかを示す boolean
startTransition処理をトランジション(低優先度)としてマークする関数

動作の仕組み

通常の状態更新:
  入力 → 状態更新 → 重い再レンダリング → UI が固まる → 次の入力を受け付ける

useTransition を使った更新:
  入力 → 通常の状態更新(即時)
       → startTransition の状態更新(バックグラウンドで処理)
       → ユーザーは操作を続けられる

基本的な使い方

import { useState, useTransition } from "react";
 
function SearchPage() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
 
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // ← 入力は即時反映(高優先度)
 
    startTransition(() => {
      // ← 検索結果の更新は低優先度(バックグラウンド)
      setResults(filterItems(value));
    });
  };
 
  return (
    <>
      <input value={query} onChange={handleChange} placeholder="検索..." />
 
      {isPending ? (
        <p>検索中...</p>
      ) : (
        <ul>
          {results.map((item) => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    </>
  );
}

実用例:タブ切り替えのUX改善

重いコンテンツを持つタブを切り替えるときに useTransition が役立ちます。

import { useState, useTransition } from "react";
 
function TabContainer() {
  const [tab, setTab] = useState("home");
  const [isPending, startTransition] = useTransition();
 
  const switchTab = (newTab) => {
    startTransition(() => {
      setTab(newTab); // タブの切り替えを低優先度で処理
    });
  };
 
  return (
    <div>
      <nav>
        {["home", "about", "posts"].map((t) => (
          <button
            key={t}
            onClick={() => switchTab(t)}
            style={{ opacity: isPending ? 0.6 : 1 }}
          >
            {t}
          </button>
        ))}
      </nav>
 
      {/* isPending 中は現在のタブを薄く表示し続ける */}
      <div style={{ opacity: isPending ? 0.5 : 1 }}>
        <TabContent tab={tab} />
      </div>
    </div>
  );
}

useTransition を使わない場合、新しいタブのコンテンツが描画されるまで画面が固まります。useTransition を使うと、処理中も現在のタブを表示し続け、isPending で「切り替え中」の視覚的フィードバックを提供できます。

使えない場面:入力の制御

入力フィールドの state には startTransition を使えません。

// ❌ NG:入力値の state はトランジションにできない
const handleChange = (e) => {
  startTransition(() => {
    setInputValue(e.target.value); // これは動作しない
  });
};
 
// ✅ OK:入力値は即時更新、表示用のデータをトランジションにする
const handleChange = (e) => {
  setInputValue(e.target.value); // 即時更新
  startTransition(() => {
    setDisplayData(processData(e.target.value)); // 低優先度
  });
};

useTransition と useDeferredValue の使い分け

フック対象使う場面
useTransition状態更新のコード自分でイベントハンドラを書ける場合
useDeferredValue受け取った値props や外部からの値を遅延させたい場合

注意点

  • startTransition に渡す関数は同期的に実行される
  • await の後の状態更新は別の startTransition でラップが必要
  • トランジション中にユーザーが別の操作をすると、処理は中断される

まとめ

  • useTransition は状態更新の優先度を下げてUIの応答性を維持するフック
  • isPending でトランジション中のローディング表示ができる
  • 重いリスト描画・タブ切り替えなどに効果的
  • 入力フィールドの state には使えない
  • props を受け取る側は useDeferredValue が適している

関連記事