LoginSignup
5
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【useEffect】《初回には実行しない》は不可能だけど《ステートを見て実行中止する》は可能です

Last updated at Posted at 2024-06-29

useEffect を使って「初回以外の再レンダリング時に実行される処理」を書くにはどうすれば良いのか?

という疑問を、たまに目にします。

前回の記事では、「useEffect ではなくイベントハンドラに処理を移動することで、《どのユーザーイベントに反応して、そのステートの遷移を引き起こすのか》を明確にする」グッドプラクティスについて説明しましたが、

これだけでは「エフェクトが初回に実行されてほしくない…」と困るケースの全てがカバーしきれていないと思います。


この記事では、もう一つのケース、「エフェクトが初回に実行されてほしくない」けど、《ステートと外部の同期》という本来の用途に当てはまるケースについて解説します。

本題: ステートによって実行する/スキップを制御する

イベントハンドラに移すような処理ではないけど、「初回レンダリング(マウント)時には実行せず、再レンダリング以降にのみ実行される」という仕様を実現したい…

という場合は、前回の記事でも出てきた《コンポーネントのステートに着目する》観点で、仕様そのものを見直してみましょう。

  • ルームID 入力欄に入力するたびに、その roomId に対応するチャットルームに接続しなおす
  • ただし、初回レンダリング時には呼ばない

こちらは React 公式ドキュメントでの題材をアレンジしたものです。

「初回レンダリング時には呼ばない」とありますが、「roomId が空文字のとき」に言い換えられるのではないでしょうか?

  • ルームID 入力欄に入力するたびに、その roomId に対応するチャットルームに接続しなおす
  • ただし、 初回レンダリング時 roomId が空文字のとき には呼ばない

「いま、このエフェクト実行は、初回レンダリングか、それ以降のレンダリングなのか」を知ることはできませんが、

「コンポーネントのステートたちが、このようなときには実行する、あのようなきには実行しない」というふうに条件を整理することで、エフェクトを使った実装が可能になりました。

export const App: FC = () => {
  const [roomId, setRoomId] = useState("");
  
  useEffect(() => {
    if(!roomId) return; // roomId が空のときは実行しない
    
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <div>
      <div>roomId: {roomId}</div>
      <div>
        <input
          type="text"
          value={roomId}
          onChange={(e) => setRoomId(e.target.value)}
        />
      </div>
    </div>
  );
}

初期値にさまざま可能性がある場合

仕様が変わりました。 roomId の初期値が空でない文字になることが場合によってありえます。

今のままだと「初回だけど実行されてしまう」のでは?

export const Chatting: FC<Props> = ({ defaultRoomId = "" }) => {
  const [roomId, setRoomId] = useState(defaultRoomId);

このようなコードで書くやつですね。

むしろ、そのような仕様であれば、初回に実行されるほうが正しいのではないかと思いますが…

どうしても初回だけ特別に実行を阻止したいなら、ステートを導入すると可能です。

export const Chatting: FC<Props> = ({ defaultRoomId = "" }) => {
  const [roomId, setRoomId] = useState(defaultRoomId);

  // createConnection の実行制御に使う。
  // 入力欄への入力が一度でもあったら true に倒す。
  const [shouldConnect, setShouldConnect] = useState(false);

  useEffect(() => {
    if(!roomId) return;
    if(!shouldConnect) return; 
    
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [shouldConnect, roomId]);

// 中略
        <input
          type="text"
          value={roomId}
          onChange={(e) => {
            setRoomId(e.target.value);
            setShouldConnect(true)
          }}
        />

余談

今回のケースは React のエフェクトの特色が生きていると言えそうです。

エフェクトの関数の中でステートの値を読み取る(クロージャの用語を使うと「キャプチャする」)ように書いた場合、エフェクトは《スナップショットとして振る舞うステート》と上手く連携できる仕組みになっています。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0