初学者が関数型コンポーネントを使って困ったこと


はじめに

今までクラス型でコンポーネントを書いていた私が関数型のコンポーネントを書く際に困ったことを書きたいと思います。
今回は「3秒たった後に数字が0から1に変わる」という機能を実装する中で僕が出会った問題を紹介したいと思います。

問題点

関数型コンポーネントを使って書いてみます。

index.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function App() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  useEffect(() => {
    setTimeout(increment, 3000);
  });
  return (
    <div>
      <h1>Sample Counter</h1>
      <p>{count}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

これで実行すると3秒おきに延々とカウントが進むようになります。

解決策

以下のように書くと、実行後に3秒後に1回だけカウントが進むようになります。

index.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function App() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  useEffect(() => {
    setTimeout(increment, 3000);
  },[]);
  return (
    <div>
      <h1>Sample Counter</h1>
      <p>{count}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

ほとんど変更していないように見えるがuseEffectの第2引数に[]を渡しています。
第2引数に値を渡すと、再レンダー時、その値が変わっていなければ副作用の適用をスキップするようになります。
今回空の配列を渡しているので、useEffect内部の式は最初に一回だけ実行され、その後再実行されることはありません。
今回のような場合、第2引数に空の配列を渡し、明示的に再実行の必要がないことをReact側に伝える必要があるようです。

最後に

このようなことは、以下のReactのドキュメントにも書いてあるので、もう少ししっかりドキュメントを読まないとなと思いました。