Reactでコンポーネントの更新をstartTransitionでラップする
表示切り替えのあるコンポーネントで下記のエラーが発生しました。
どうやら切り替えに必要な情報の 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 は現在のタスクを中断し、更新をマークしてから、ブラウザが次のフレームをレンダリングする前に、その更新を反映するようにスケジュールします。
Discussion