🦁

Reactでコンポーネントの更新をstartTransitionでラップする

2023/04/05に公開

表示切り替えのあるコンポーネントで下記のエラーが発生しました。
どうやら切り替えに必要な情報の load 状態が長く、React がレンダリングを中断してコンポーネントの状態が保持されなくなる状態の場合に発生するエラーのようです。

A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.

対処法

原因となるデータをstartTransitionの関数でラップする必要があります。
例えば下記のように、props に渡ってくる非同期データによってコンポーネントの表示/非表示が変わる場合などに下記のように対応することで上記のエラーを回避できます。

import { useTransition } from "react";
// ...

export const View = (props: Props) => {
  const { data, className, ...props } = props;
  const [loading, setLoading] = useState(false);
  const [_, startTransition] = useTransition();
  useEffect(() => {
    startTransition(() => {
      setLoading(!!data);
    });
  }, [data]);

  return <View>{loading ? <Loading /> : <Component />}</View>;
};

useTransition とは

React のフックの 1 つで、非同期の更新をトランジション可能な状態にするためのものです。ユーザーが更新が完了するまで待たされることなく、アプリケーションの他の部分を操作できるようにすることができます。

useTransition を使用すると、更新の開始と終了の状態を管理でき、アニメーションなどのトランジションを適用できます。
また、トランジションの中にある間に別の更新が発生しても、アプリケーションの安定性を保つことができます。

useTransition は、React 18 で新しく導入された Concurrent Mode に最適化されており、これにより、アプリケーションがスムーズに動作し、更新が遅れることがなくなるとされています。
上記のstartTransitionに加えてisPendingを使った例は下記の通りです。

import { useState, useTransition } from "react";

function ExampleComponent() {
  const [text, setText] = useState("");
  const [isPending, startTransition] = useTransition({
    timeoutMs: 3000,
  });

  const handleClick = () => {
    startTransition(() => {
      setText("Loading...");
      // 重い処理
      setText("Loaded!");
    });
  };

  return (
    <div>
      <p>{text}</p>
      <button onClick={handleClick} disabled={isPending}>
        {isPending ? "Loading..." : "Hello!"}
      </button>
    </div>
  );
}

isPendingは、現在の非同期処理がまだ終了していない場合に true を返します。
startTransition は、非同期処理を開始するための関数です。
startTransition が呼び出されると、React は現在のタスクを中断し、更新をマークしてから、ブラウザが次のフレームをレンダリングする前に、その更新を反映するようにスケジュールします。

GitHubで編集を提案

Discussion