🍣

値を一定時間保持するhooksを作ってみる

2024/04/30に公開

始めに

ローディングなどのフラグを管理する場合、500ms以上かかる場合はローディングUIも確認できるため良いことですが、50msなどかなり短い時間で終わる場合は逆にチカチカして個人的には違和感がありました。

これを一定時間過去の値を保持するようなhooksを作ったら時間がかかる時だけローディングUIを表示することができUX的に良くなるのでは思い作ってみました。

実装方法

実装自体はそこまで難しくなく、hooksで渡される値をステートで管理して、useEffectで変更が入る度にoptionsに渡したkeepTime分だけ待ってから更新するように実装します。keepTimeは number で固定の数値を渡す以外に、次の値を見てから待ち時間を決定できるようにしています。

値を保持するhooks
/**
 * 値を保持するhooks
 * @param value - 値
 * @param options.keepTime - 値を維持する時間
 */
const useKeepValue = function <T>(
  value: T,
  options: {
    keepTime: number | ((nextValue: T, currentValue: T) => number);
  }
) {
  const [currentValue, setCurrentValue] = useState(value);
  const { keepTime } = options;

  useEffect(() => {
    // 値が同じ場合は更新しない
    if (value === currentValue) {
      return;
    }

    const waitTime =
      typeof keepTime === 'number' ? keepTime : keepTime(currentValue, value);
    // 待ち時間がない場合は即時反映する
    if (waitTime === 0) {
      setCurrentValue(value);
      return;
    }

    // 待ち時間がある場合はその時間までsetTimeoutで待つ
    const timerId = setTimeout(() => {
      setCurrentValue(value);
    }, waitTime);
    return () => {
      clearTimeout(timerId);
    };
  }, [value, currentValue, keepTime]);

  return currentValue;
};

keepTime にfunctionを渡せるようにしているのは、例えば以下のようにローディング中から解除する時は即時変更で良いけど、未ローディングからローディングへ遷移するときは一定時間待ちたいというように、値に応じて維持したい時間が変わるためです。

useKeepValueの使用例
const keepedIsLoading = useKeepValue(isLoading, {
  keepTime: (currentIsLoading) => {
    // ローディング中の時はすぐ変更してよく、未ローディングの時は100ms待ってから変更されるようにする
    return currentIsLoading ? 0 : 100;
  },
});

終わりに

以上が値を一定時間保持する方法でした。変更が早くてチラつきが気になる際の対処法の一つとして参考になると幸いです。
最後に検証コードをStackBlitzで書きましたので、動作を確認したい方はこちらを見ていただければ思います。

GitHubで編集を提案

Discussion