8.1 Effectの目的

Effectは、Reactコンポーネントを外部システムと同期するために使う。

外部システムの例:

  • WebSocket
  • DOM API
  • タイマー
  • サードパーティ製UI
  • ブラウザイベント
  • ネットワーク購読
  • 分析SDK

8.2 基本形

useEffect(() => {
  const connection = createConnection(roomId);
  connection.connect();

  return () => {
    connection.disconnect();
  };
}, [roomId]);
  • setup: 同期を開始する
  • cleanup: 同期を停止する
  • dependencies: 再同期が必要な値

8.3 Effectが不要な処理

派生値

// 不要
const [fullName, setFullName] = useState("");

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

// 推奨
const fullName = `${firstName} ${lastName}`;

ユーザー操作への応答

// 不要: stateを監視してイベント処理する
useEffect(() => {
  if (submitted) {
    postForm();
  }
}, [submitted]);

// 推奨
async function handleSubmit() {
  await postForm();
}

props変更によるstateリセット

Effectよりkeyを検討する。

<ProfileForm key={userId} userId={userId} />

8.4 依存配列

Effect内部で参照するリアクティブ値は依存関係に含める。

useEffect(() => {
  document.title = `${projectName} - ${count}`;
}, [projectName, count]);

依存配列は「実行タイミングを選ぶスイッチ」ではない。コードから導かれる同期条件である。

8.5 クリーンアップ

useEffect(() => {
  function handleResize() {
    setWidth(window.innerWidth);
  }

  window.addEventListener("resize", handleResize);
  handleResize();

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

購読、タイマー、接続などは必ず対応する解除処理を考える。

8.6 競合状態

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

  async function load() {
    const result = await fetchUser(userId);

    if (!ignore) {
      setUser(result);
    }
  }

  load();

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

実務では、AbortControllerやデータ取得ライブラリ・フレームワークのローダーを利用する。

8.7 useLayoutEffect

DOM更新後、ブラウザ描画前に同期処理する。

useLayoutEffect(() => {
  const rect = ref.current?.getBoundingClientRect();
  setHeight(rect?.height ?? 0);
}, []);

描画をブロックするため、通常はuseEffectを優先する。レイアウト計測など視覚的なちらつきを防ぐ必要がある場合に限定する。

8.8 useEffectEvent

React 19.2のuseEffectEventは、Effectから呼び出す非リアクティブな処理を分離する。

const onConnected = useEffectEvent(() => {
  showNotification("接続しました", theme);
});

useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.on("connected", onConnected);
  connection.connect();

  return () => connection.disconnect();
}, [serverUrl, roomId]);

themeの変更だけでは接続をやり直さず、通知では最新のthemeを使える。