useEffectでrace conditionとdebounceに対応する
ReactのHooksに入門した。
useEffectのクリーンアップは再レンダー時に毎回行われるんだよ!という公式ドキュメントをみて、ほーんという感じだったけど
race condition
とdebounce
をhooksを使ってシンプルに対応するサンプルを見て、なんだか凄さを感じた。
やりたいこと
以下のように、入力内容の変更がある度にAPIリクエストをして、結果を反映させるということをしてみたい。
やってみる
これをuseEffectを使って書くと、以下のようになる。
const [name, setName] = useState("taro");
const [age, setAge] = useState(0)
useEffect(() => {
setLoading(true);
getUserData(name).then(age => {
setAge(age);
setLoading(false);
}
});
}, [name])
このコードにはいろいろ問題があるのだけど、最大の問題は最終的にレンダリングされるAPIの結果が、想定と異なる可能性がある点。
というのも、APIは呼び出した順に結果が返ってくるという保障もないので、最後に返ってきたAPIの結果が最後に呼び出したAPIの結果とは限らないから。
これを解決するためには、入力途中に呼び出されたAPIの結果は無視する必要がある。
この文章だけみると、やけに難しく感じるのだけど、あら不思議。
Hooksの公式ドキュメントの記載に則り、副作用のクリーンアップを利用すると簡単に対応できる。
新しい副作用を適用する前に、ひとつ前の副作用をクリーンアップします。
const [name, setName] = useState("taro");
const [age, setAge] = useState(0)
useEffect(() => {
let cancel = false
setLoading(true);
getUserData(name).then(age => {
if(!cancel) {
setAge(age);
setLoading(false);
}
}
return () => {
cancel = true
}
});
}, [name])
これを利用すると、debounce処理もさくっと実装できそう。
useEffect(() => {
setLoading(true);
const timeOutId = setTimeout(() => {
getUserData(name).then(age => {
setAge(age);
setLoading(false);
});
}, 1000);
return () => {
clearTimeout(timeOutId);
};
}, [name]);
実際には、以下のようなuseDebounceのようなカスタムHookも作ると汎用的になる。
https://github.com/xnimorz/use-debounce