Shadcn Hooks

useDebounceEffect

A hook to debounce an effect

Loading...

Installation

npx shadcn@latest add @hooks/use-debounce-effect
pnpm dlx shadcn@latest add @hooks/use-debounce-effect
yarn dlx shadcn@latest add @hooks/use-debounce-effect
bun x shadcn@latest add @hooks/use-debounce-effect

Copy and paste the following code into your project.

use-debounce-fn.ts
import { debounce } from 'es-toolkit'
import { useMemo } from 'react'
import { useLatest } from '@/registry/hooks/use-latest'
import { useUnmount } from '@/registry/hooks/use-unmount'
import type { DebounceOptions } from 'es-toolkit'

export type { DebounceOptions }

export function useDebounceFn<Fn extends (...args: any[]) => any>(
  fn: Fn,
  debounceMs?: number,
  options?: DebounceOptions,
) {
  const fnRef = useLatest(fn)

  const debouncedFn = useMemo(
    () =>
      debounce(
        (...args: Parameters<Fn>) => fnRef.current(...args),
        debounceMs ?? 1000,
        options,
      ),
    [],
  )

  useUnmount(() => debouncedFn.cancel())

  return {
    run: debouncedFn,
    cancel: debouncedFn.cancel,
    flush: debouncedFn.flush,
  }
}

use-update-effect.ts
import { useEffect, useRef } from 'react'
import { useUnmount } from '@/registry/hooks/use-unmount'
import type { DependencyList, EffectCallback } from 'react'

export function useUpdateEffect(effect: EffectCallback, deps: DependencyList) {
  const mounted = useRef(false)

  // for react-refresh
  useUnmount(() => {
    mounted.current = false
  })

  useEffect(() => {
    if (!mounted.current) {
      mounted.current = true
      return
    }

    return effect()
  }, deps)
}

use-debounce-effect.ts
import { useEffect, useState } from 'react'
import { useDebounceFn } from '@/registry/hooks/use-debounce-fn'
import { useUpdateEffect } from '@/registry/hooks/use-update-effect'
import type { DependencyList, EffectCallback } from 'react'
import type { DebounceOptions } from '@/registry/hooks/use-debounce-fn'

export function useDebounceEffect(
  effect: EffectCallback,
  deps: DependencyList,
  debounceMs?: number,
  options?: DebounceOptions,
) {
  const [flag, setFlag] = useState({})
  const { run } = useDebounceFn(
    () => {
      setFlag({})
    },
    debounceMs,
    options,
  )

  useEffect(() => {
    return run()
  }, deps)

  useUpdateEffect(() => {
    return effect()
  }, [flag])
}

API

interface DebounceOptions {
  /**
   * An optional AbortSignal to cancel the debounced function.
   */
  signal?: AbortSignal
  /**
   * An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
   * If `edges` includes "leading", the function will be invoked at the start of the delay period.
   * If `edges` includes "trailing", the function will be invoked at the end of the delay period.
   * If both "leading" and "trailing" are included, the function will be invoked at both the start and end of the delay period.
   * @default ["trailing"]
   */
  edges?: Array<'leading' | 'trailing'>
}

/**
 * A hook to debounce an effect
 * @param effect - The effect to debounce
 * @param deps - The dependencies to debounce the effect on
 * @param debounceMs - The debounce time in milliseconds default to 1000
 * @param options - The options for the debounce effect
 * @returns The debounced effect
 */
export function useDebounceEffect(
  effect: EffectCallback,
  deps: DependencyList,
  debounceMs?: number,
  options?: DebounceOptions,
): void

Credits

Last updated on