<p>基本的なところだけど、少し悩んでしまったのでメモ。</p> <p>アプリのタイマー機能を実装しようとした際、setIntervalを使用しても時間が加算されない問題が発生した。 原因としては、<b>setStateにオブジェクトを渡した場合、stateを即座にアップデートすることを保証しない</b>というもの。</p> <p><a href="https://ja.reactjs.org/docs/faq-state.html#how-do-i-update-state-with-values-that-depend-on-the-current-state">&#x30B3;&#x30F3;&#x30DD;&#x30FC;&#x30CD;&#x30F3;&#x30C8;&#x306E; state &ndash; React</a></p> <p>今回のような、setIntervalでsetStateが呼ばれるなど参照したいstateが絶えず更新されているものである場合、「setStateに更新用の関数を渡し、その関数内の引数で最新のstateにアクセスして操作する」のが正しい実装内容となる。</p> <h4>駄目な例</h4> <p>下記の例だと、<code>setState</code>にtimeに1を足した値を引数として渡しているが、この時にtimeが参照しているのは更新前のtimeである。 つまり、0秒から時間の加算が始まったとして、</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synComment">// timeは親のコンポーネントからもらってくる数値</span> <span class="synStatement">const</span> <span class="synIdentifier">[</span>time, setTime<span class="synIdentifier">]</span> = useState(props.time || <span class="synStatement">null</span>); useEffect(() =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> <span class="synIdentifier">{</span> taskId, recordingTaskId <span class="synIdentifier">}</span> = props; <span class="synStatement">if</span> (taskId &amp;&amp; taskId === recordingTaskId) <span class="synIdentifier">{</span> <span class="synComment">// setIntervalで時間加算の関数(addSecond)を1秒毎に実行させる</span> setTimerId(setInterval(addSecond, 1000)); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">return</span>; <span class="synIdentifier">}</span>, <span class="synIdentifier">[</span>props.recordingTaskId<span class="synIdentifier">]</span>); <span class="synStatement">const</span> addSecond = () =&gt; <span class="synIdentifier">{</span> <span class="synComment">// ここでsetStateに「time + 1」という値を渡してしまっているため、常に古いstateを見てしまっている</span> setTime(time + 1); <span class="synIdentifier">}</span>; </pre> <h4>良い例</h4> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synComment">// timeは親のコンポーネントからもらってくる数値</span> <span class="synStatement">const</span> <span class="synIdentifier">[</span>time, setTime<span class="synIdentifier">]</span> = useState(props.time || <span class="synStatement">null</span>); useEffect(() =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> <span class="synIdentifier">{</span> taskId, recordingTaskId <span class="synIdentifier">}</span> = props; <span class="synStatement">if</span> (taskId &amp;&amp; taskId === recordingTaskId) <span class="synIdentifier">{</span> <span class="synComment">// setIntervalで時間加算の関数(addSecond)を1秒毎に実行させる</span> setTimerId(setInterval(addSecond, 1000)); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">return</span>; <span class="synIdentifier">}</span>, <span class="synIdentifier">[</span>props.recordingTaskId<span class="synIdentifier">]</span>); <span class="synStatement">const</span> addSecond = () =&gt; <span class="synIdentifier">{</span> <span class="synComment">// 更新関数を渡しているため、この中のtは最新のstateが入っていることが保証されている</span> setTime(t =&gt; t + 1); <span class="synIdentifier">}</span>; </pre>