株式会社ムラウチドットコムのフロントエンドエンジニアです。
ムラウチドットコムのOrganizationができて初めての投稿です。
今回はユーザーの入力で値が確定してから実行されるカスタムuseEffect
を考えたので紹介します。
コード
import React from "react";
export const useDelayedEffect = (
effect: React.EffectCallback,
deps?: React.DependencyList,
delaytime: number = 1000,
) => {
const [waiting, setWaiting] = React.useState(false);
const timer = React.useRef<number>();
React.useEffect(() => {
window.clearTimeout(timer.current);
setWaiting(true);
timer.current = window.setTimeout(() => {
setWaiting(false);
}, delaytime);
}, deps);
React.useEffect(() => {
if (!waiting) {
effect();
}
}, [waiting]);
};
使い方
普通のuseEffect
の引数に加えて入力が確定したと判断するまでのdelaytime
を指定するだけです。
import React from "react";
import { useDelayedEffect } from "./useDelayedEffect";
export const App = () => {
const [text, setText] = React.useState("");
const [confirmedText, setConfirmedText] = React.useState("");
useDelayedEffect(
() => {
setConfirmedText(text);
},
[text],
1500,
);
return (
<div>
<input onChange={e => setText(e.target.value)} value={text} />
<p>{`text: ${text}`}</p>
<p>{`confirmedText: ${confirmedText}`}</p>
</div>
);
};
解説
入力完了を待っているかどうかの状態をwaiting
で保持します。
ひとつめのuseEffect
でwaiting
をtrue
にしてdelaytime
ミリ秒後にwaiting
をfalse
にするタイマーをセットします。
useEffect
は入力1文字1文字で実行されるので、その都度タイマーを解除してdelaytime
を測り直しています。
レンダリングのたびにカスタムフック関数が実行されるため、
let timer;
// 中略
window.clearTimeout(timer);
// 中略
timer = window.setTimeout(() => setWaiting(false), delaytime);
では実装できないことに注意してください(毎回timer
が初期化されるため)。useRef
を用いることで再レンダリングされても参照できる変数を定義します。
ふたつめのuseEffect
でwaiting
がtrue
からfalse
になったとき、つまり入力がdelaytime
ミリ秒以上行われなかったときに引数effect
関数を実行します。
ユースケース
ユーザーの入力値を自動保存する場合などに利用できると思います。
通常のuseEffect
に保存のためのHTTPリクエストを仕込んでおくと、例えば "Hello world." を入力すると12回HTTP送信してしまいます。
そうではなくユーザーの入力が終わってから送信すれば無駄なHTTPリクエストをへらすことができます。
まとめ
入力が確定してから実行されるuseEffect
を紹介しました。
もっといい書き方やすでに実装されているライブラリ等あったらぜひおしえてください。
追記
この書き方で十分なことに気づいた!
export function useDelayedEffect(
effect: React.EffectCallback,
deps: React.DependencyList,
timeout: number = 1000,
) {
useEffect(() => {
const timeoutId = setTimeout(effect, timeout);
return () => clearTimeout(timeoutId);
}, deps);
}