2021/09/18

useEffect内のsetIntervalでstateを更新できない

@ 酒井悠宇

useEffect内にstateを書き換えるsetIntervalを記述したところ想像と違う挙動になったのでメモ的な感じで記事書いていきたいと思います。

Next.jsで作成しているサイトの初回読み込み時に、0から100までのカウントアップを行いたい場面(ローディングアニメーション的なの)があったので、以下のようなstateを用意し、

const [count, setCount] = useState(0);


pagesディレクトリ配下の_app.tsx内のuseEffectに以下のようなコードを記述しました。

  useEffect(() => {
    const loading = setInterval(() => {
      setCount((prevState) => prevState + 1);
      console.log(count);
      if (count >= 100) {
        clearInterval(loading);
      }
    }, 1000);
  }, []);


表示はjsx内で以下のようにしています。

<div>{count}</div>


countが100になった時にclearIntervalを走らせてsetIntervalを止めたかったのですが、実際にはcountが100以上になってもclearIntervalが走らずに、無限ループが起こってしまう挙動になりました。

なぜそのようになるのかを調べたところ、以下のような記事を見つけました。

useEffect内のsetIntervalでstateを更新できない問題

この記事には、「useEffect内の変数は実行時に切り出される為、内部で変数を変更しても、内部では反映されない 」と書いてありました。いまいちピンとこないですね。

実際に以下のようにuseEffect内にconsole.logを置いて

  useEffect(() => {
    const loading = setInterval(() => {
      setCount((prevState) => prevState + 1);
      console.log(count);
      if (count >= 100) {
        clearInterval(loading);
      }
    }, 23);
  }, []);


コンソールを確認してみます

> 0
> 0
> 0
> 0
> 0
・
・
・


タブを閉じない限り永遠に0が表示されてしまうという挙動になっています。
つまり「setIntervalが走っていない = setCount関数内部でのcountの更新がsetInterval関数内部には反映されていない」ということっぽいです。

これだと思い通りの挙動にはなりそうにないですね。

これをいい感じにするためには以下のように記述します。

  useEffect(() => {
    //stateを書き換えるようの変数を用意
    let n = 0;
    //setInterval実行
    const loading = setInterval(() => {
      //実行されるごとに n+1 を行う
      n++;
      //更新されたnをもとにstateを更新する
      setCount(n);
      //nが100以上になったらsetIntervalを止める
      if (n >= 100) {
        clearInterval(loading);
      }
    }, 23);
  }, []);


変数の変更をsetCount内部で行うのではなく、setInterval内部で行うようにしています。
こうすることによって、変数の変更がsetInterval内部にも反映されるようになるので、いい感じになります。

---

これでいい感じにカウントアップしながらstateを書き換えるロジックを作ることができました!
最後までごらんいただきありがとうございました!

© 2021 powerd by UnReact