非同期の処理が実行されたとき最新の状態を得るためのフックuseLatest
のご紹介です。
関数がつくられたときの状態を保つのがReactの原則
Reactの「コンポーネント内に書かれた関数からは、その関数が作成された時のprops
や state
が『見え』」ます(「関数内で古い props や state が見えているのはなぜですか?」原文:「Why am I seeing stale props or state inside my function?」)。外から勝手に書き替えられることによるバグが防げるので安心です。
もっとも、非同期の処理では、関数が実行されたときの最新の状態を使いたいことも少なくありません。React公式サイトの「フックに関するよくある質問」には、つぎのようなコード例が示されています。
コード001■関数はつくられたときの状態変数の値を保つ
import React from 'react';
function Example() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
}
ページの[Click me]ボタンを押すと、その数だけ数値が加算されます。[Show alert]はその数値を3秒後に警告ダイアログで表示するボタンです。ただし、その値はボタンを押したときのまま保たれます(図001)。あとにカウントアップした値は見えないのです。以下のCodePenのサンプルで、動きは確かめられます。
図001■警告ダイアログにはボタンを押したときの値が示される
See the Pen React: Seeing stale props by Fumio Nonaka (@FumioNonaka) on CodePen.
これが意図した結果であれば構いません。けれど、最新の値を使って処理したい場合もあるはずです。
useLatestフックで最新の状態を得る
それでは、非同期の処理で最新の状態を得るにはどうしたらよいでしょう。前出「関数内で古い props や state が見えているのはなぜですか?」はつぎのように説明します。
非同期的に実行されるコールバック内で、意図的に
state
の最新の値を読み出したいという場合は、その値をref
内に保持して、それを書き換えたり読み出したりすることができます。
何を言っているのかよくわからない、あるいは手間がかかりそう、と思った人は、react-use
のuseLatest
を使うとよいでしょう。インストールはnpmで行います。
npm i react-use
フックuseLatest
の戻り値は、useRef
と同じref
オブジェクトです。ただし、引数には最新値がほしい状態変数を渡します。ref
オブジェクトですからcurrent
プロパティをもち、その中に収められているのが最新の値です。
前掲Reactサイトのコード例に組み込めば、つぎのようになります。モジュールで用いるときは、useLatest
をあらかじめimport
してください。これで警告ダイアログは、コールバックが呼び出されたときの最新の状態変数値(count
)を示します。
コード001■useLatestフックで最新の状態変数の値を得る
import React from 'react';
import { useLatest } from 'react-use';
const Example = () => {
const [count, setCount] = React.useState(0);
const latestCount = useLatest(count);
function handleAlertClick() {
setTimeout(() => {
alert(`Latest count value: ${latestCount.current}`);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
};
モジュール化したサンプルをCodeSandboxに掲げました。useLatest
を用いたコードは、src/App.js
でお確かめください。
useLatest
の実装は10行足らず
おまけとして、フックuseLatest
の実装を確かめておきましょう。TypeScriptファイルのソースコードを見ると、なんと10行にも及びません。そして、useRef
フックが用いられています。前述のReactのドキュメントが説明していた「意図的にstate
の最新の値を読み出したいという場合は、その値をref
内に保持して」というのは、こういうことだったのです。