0
1

More than 3 years have passed since last update.

おすすめ自作 React hooks集4 (useMemoBuffered)

Last updated at Posted at 2020-01-26

おすすめ自作 React hooks 集 4

公式ドキュメントへのリンク

useMemoBuffered

useMemo (→ フック API リファレンス#useMemo) でメモ化したい値が、頻繁に更新される他の値に依存しており、一定時間(200msとか)その更新が止むまで再計算を待ちたい、という状況がたまに存在します(たとえばinput要素の入力された文字列に基づきリストをフィルタリングしたいときなど)。

メモ化したい値が頻繁に更新される値に依存しているが再計算も毎回必要というわけではなく適宜間引きたいという状況では、次のようなcustom hooksが役に立ちます。(RxJSの debounceTime オペレータのような動作です。)

import { useEffect, useRef, useState } from 'react';

export function useMemoBuffered<ResultValue>(
  fn: () => ResultValue,
  deps: any[],
  bufferMilliSec: number
): ResultValue {
  const ref = useRef<{ timerId: number | undefined }>({ timerId: undefined });

  const [value, setValue] = useState<ResultValue>(fn);

  useEffect(
    () => {
      // 前の予約をキャンセル
      clearTimeout(ref.current.timerId);
      // fnの実行をbufferMilliSecミリ秒後に予約
      ref.current.timerId = setTimeout(() => {
        setValue(fn());
      }, bufferMilliSec);
    },
    deps // eslint-disable-line react-hooks/exhaustive-deps
  );

  return value;
}

使い方はほとんど useMemo と同じです。

export const SomeComponent = () => {
  const [name, setName] = useState<string>('');

  // nameの値が更新されたとき、その更新が200ミリ秒間止むまで待ってからresultを再計算する。
  const result = useMemoBuffered(() => computeExpensiveValue(name), [name], 200);

  return (
    <div>
      <input value={name} onChange={event => setName(event.target.value)} />
      <SomeChildComponent result={result} />
    </div>
  )
}

2020/03/06追記

useEffectクリーンアップを忘れていたためメモリリークの危険がありました。正しくは以下のように書き、useMemoBuffered hooks が使われるcomponentが破棄されたときにtimerを破棄する必要があります。

import { useEffect, useRef, useState } from 'react';

export function useMemoBuffered<ResultValue>(
  fn: () => ResultValue,
  deps: any[],
  bufferMilliSec: number
): ResultValue {
  const ref = useRef<{ timerId: number | undefined }>({ timerId: undefined });

  const [value, setValue] = useState<ResultValue>(fn);

  const clearTimer = useCallback(() => {
    clearTimeout(ref.current.timerId);
  }, []);

  useEffect(
    () => {
      // 前の予約をキャンセル
      clearTimer()
      // fnの実行をbufferMilliSecミリ秒後に予約
      ref.current.timerId = setTimeout(() => {
        setValue(fn());
      }, bufferMilliSec);

      return clearTimer; // ← これが必要
    },
    deps // eslint-disable-line react-hooks/exhaustive-deps
  );

  return value;
}

おすすめ自作 React hooks集は随時追加予定です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1