• Stars
    star
    2,980
  • Rank 15,177 (Top 0.3 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 6 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A debounce hook for react

Features

Install

yarn add use-debounce
# or
npm i use-debounce --save

Copy paste guidance:

use-debounce

Simple usage: https://codesandbox.io/s/kx75xzyrq7

Debounce HTTP request: https://codesandbox.io/s/rr40wnropq

Debounce HTTP request with leading param: https://codesandbox.io/s/cache-example-with-areas-and-leading-param-119r3i

use-debounce callback

Simple usage: https://codesandbox.io/s/x0jvqrwyq

Combining with native event listeners: https://codesandbox.io/s/32yqlyo815

Cancelling, maxWait and memoization: https://codesandbox.io/s/4wvmp1xlw4

HTTP requests: https://codesandbox.io/s/use-debounce-callback-http-y1h3m6

Changelog

https://github.com/xnimorz/use-debounce/blob/master/CHANGELOG.md

Simple values debouncing

According to https://twitter.com/dan_abramov/status/1060729512227467264

import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';

export default function Input() {
  const [text, setText] = useState('Hello');
  const [value] = useDebounce(text, 1000);

  return (
    <div>
      <input
        defaultValue={'Hello'}
        onChange={(e) => {
          setText(e.target.value);
        }}
      />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </div>
  );
}

This hook compares prev and next value using shallow equal. It means, setting an object {} will trigger debounce timer. If you have to compare objects (#27 (comment)), you can use useDebouncedCallback, that is explained below:

Debounced callbacks

Besides useDebounce for values you can debounce callbacks, that is the more commonly understood kind of debouncing. Example with Input (and react callbacks): https://codesandbox.io/s/x0jvqrwyq

import { useDebouncedCallback } from 'use-debounce';

function Input({ defaultValue }) {
  const [value, setValue] = useState(defaultValue);
  // Debounce callback
  const debounced = useDebouncedCallback(
    // function
    (value) => {
      setValue(value);
    },
    // delay in ms
    1000
  );

  // you should use `e => debounced(e.target.value)` as react works with synthetic events
  return (
    <div>
      <input defaultValue={defaultValue} onChange={(e) => debounced(e.target.value)} />
      <p>Debounced value: {value}</p>
    </div>
  );
}

Example with Scroll (and native event listeners): https://codesandbox.io/s/32yqlyo815

function ScrolledComponent() {
  // just a counter to show, that there are no any unnessesary updates
  const updatedCount = useRef(0);
  updatedCount.current++;

  const [position, setPosition] = useState(window.pageYOffset);

  // Debounce callback
  const debounced = useDebouncedCallback(
    // function
    () => {
      setPosition(window.pageYOffset);
    },
    // delay in ms
    800
  );

  useEffect(() => {
    const unsubscribe = subscribe(window, 'scroll', debounced);
    return () => {
      unsubscribe();
    };
  }, []);

  return (
    <div style={{ height: 10000 }}>
      <div style={{ position: 'fixed', top: 0, left: 0 }}>
        <p>Debounced top position: {position}</p>
        <p>Component rerendered {updatedCount.current} times</p>
      </div>
    </div>
  );
}

Returned value from debounced()

Subsequent calls to the debounced function debounced return the result of the last func invocation. Note, that if there are no previous invocations it's mean you will get undefined. You should check it in your code properly.

Example:

it('Subsequent calls to the debounced function `debounced` return the result of the last func invocation.', () => {
  const callback = jest.fn(() => 42);

  let callbackCache;
  function Component() {
    const debounced = useDebouncedCallback(callback, 1000);
    callbackCache = debounced;
    return null;
  }
  Enzyme.mount(<Component />);

  const result = callbackCache();
  expect(callback.mock.calls.length).toBe(0);
  expect(result).toBeUndefined();

  act(() => {
    jest.runAllTimers();
  });
  expect(callback.mock.calls.length).toBe(1);
  const subsequentResult = callbackCache();

  expect(callback.mock.calls.length).toBe(1);
  expect(subsequentResult).toBe(42);
});

Advanced usage

Cancel, maxWait and memoization

  1. Both useDebounce and useDebouncedCallback works with maxWait option. This params describes the maximum time func is allowed to be delayed before it's invoked.
  2. You can cancel debounce cycle, by calling cancel callback

The full example you can see here https://codesandbox.io/s/4wvmp1xlw4

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { useDebouncedCallback } from 'use-debounce';

function Input({ defaultValue }) {
  const [value, setValue] = useState(defaultValue);
  const debounced = useDebouncedCallback(
    (value) => {
      setValue(value);
    },
    500,
    // The maximum time func is allowed to be delayed before it's invoked:
    { maxWait: 2000 }
  );

  // you should use `e => debounced(e.target.value)` as react works with synthetic events
  return (
    <div>
      <input defaultValue={defaultValue} onChange={(e) => debounced(e.target.value)} />
      <p>Debounced value: {value}</p>
      <button onClick={debounced.cancel}>Cancel Debounce cycle</button>
    </div>
  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<Input defaultValue="Hello world" />, rootElement);

Flush method

useDebouncedCallback has flush method. It allows to call the callback manually if it hasn't fired yet. This method is handy to use when the user takes an action that would cause the component to unmount, but you need to execute the callback.

import React, { useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';

function InputWhichFetchesSomeData({ defaultValue, asyncFetchData }) {
  const debounced = useDebouncedCallback(
    (value) => {
      asyncFetchData;
    },
    500,
    { maxWait: 2000 }
  );

  // When the component goes to be unmounted, we will fetch data if the input has changed.
  useEffect(
    () => () => {
      debounced.flush();
    },
    [debounced]
  );

  return <input defaultValue={defaultValue} onChange={(e) => debounced(e.target.value)} />;
}

isPending method

isPending method shows whether component has pending callbacks. Works for both useDebounce and useDebouncedCallback:

import React, { useCallback } from 'react';

function Component({ text }) {
  const debounced = useDebouncedCallback(useCallback(() => {}, []), 500);

  expect(debounced.isPending()).toBeFalsy();
  debounced();
  expect(debounced.isPending()).toBeTruthy();
  debounced.flush();
  expect(debounced.isPending()).toBeFalsy();

  return <span>{text}</span>;
}

leading/trailing calls

Both useDebounce and useDebouncedCallback work with the leading and trailing options. leading param will execute the function once immediately when called. Subsequent calls will be debounced until the timeout expires. trailing option controls whenever to call the callback after timeout again.

For more information on how leading debounce calls work see: https://lodash.com/docs/#debounce

import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';

export default function Input() {
  const [text, setText] = useState('Hello');
  const [value] = useDebounce(text, 1000, { leading: true });

  // value is updated immediately when text changes the first time,
  // but all subsequent changes are debounced.
  return (
    <div>
      <input
        defaultValue={'Hello'}
        onChange={(e) => {
          setText(e.target.value);
        }}
      />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </div>
  );
}

Options:

You can provide additional options as a third argument to both useDebounce and useDebouncedCallback:

option default Description Example
maxWait - Describes the maximum time func is allowed to be delayed before it's invoked https://github.com/xnimorz/use-debounce#cancel-maxwait-and-memoization
leading - This param will execute the function once immediately when called. Subsequent calls will be debounced until the timeout expires. https://github.com/xnimorz/use-debounce#leading-calls
trailing true This param executes the function after timeout. https://github.com/xnimorz/use-debounce#leading-calls
equalityFn (prev, next) => prev === next [useDebounce ONLY] Comparator function which shows if timeout should be started

useThrottledCallback

You are able to use throttled callback with this library also (starting 5.2.0 version). For this purpose use:

import useThrottledCallback from 'use-debounce/useThrottledCallback';

or

import { useThrottledCallback } from 'use-debounce';

Several examples:

  1. Avoid excessively updating the position while scrolling.

    const scrollHandler = useThrottledCallback(updatePosition, 100);
    window.addEventListener('scroll', scrollHandler);
  2. Invoke renewToken when the click event is fired, but not more than once every 5 minutes.

    const throttled = useThrottledCallback(renewToken, 300000, { 'trailing': false })
    <button onClick={throttled}>click</button>

All the params for useThrottledCallback are the same as for useDebouncedCallback except maxWait option. As it's not needed for throttle callbacks.

Special thanks:

@tryggvigy — for managing lots of new features of the library like trailing and leading params, throttle callback, etc;

@omgovich — for reducing bundle size.

More Repositories

1

svelte-input-mask

Input masking component for Svelte with simple API and rich customization
Svelte
102
star
2

masked-input

Mask input with simple API and rich customization
TypeScript
52
star
3

react-providers

The React Component, which allows you to combine contexts and incapsulate to components using HOC, is built on a top of Context API
TypeScript
23
star
4

browser-extensions-talk-holyjs

JavaScript
21
star
5

on-hotkey

Native JavaScript library to subscribe on keyboard shortcuts, small and without any dependencies
TypeScript
19
star
6

selection-range-enhancer

A project, which enhances abilities of selectionRange. It allows to track changes, works with carets and so on
TypeScript
14
star
7

rprogress

React ajax loader progress bar with clear API
JavaScript
11
star
8

ex-habr

CSS
11
star
9

subscribe-event

The easiest way to subscribe and unsubscribe to browser / node events
JavaScript
8
star
10

simple-expert-system

Простейшая экспертная система
JavaScript
5
star
11

modern-frameworks-dom-invalidation-talk

Доклад о работе с DOM в современных фреймворках
HTML
5
star
12

use-handler

React-hook, that returns handler only once while component mounted. Your reference for a function won't be changed. It's useful for optimizations. Note: you can use useCallback hook, that is already included in react instead of custom useHandler hook. You can see example with useHandler and useCallback hooks here: https://codesandbox.io/s/vjko8kroyl
JavaScript
5
star
13

stackoverflow-example

JavaScript
4
star
14

textArea-selector

textArea-selector
JavaScript
4
star
15

icons-talks

Материалы по докладу <Icon />
JavaScript
4
star
16

ready

Promises/A+, ES6 promises
JavaScript
3
star
17

UIComponents

Библиотека интерфейсов
HTML
3
star
18

frontend-conf-materials

Материалы для frontend-conf — Middlewares are awesome
JavaScript
3
star
19

signal-middleware

Redux signal middleware. A place to store your business logic and async code
JavaScript
3
star
20

getItConf-web-site-performance

Дополнительные материалы по докладу "Сайт тормозит. Что делать?"
2
star
21

vanilla-masked-input

Masked inputs for using without React (Main project at https://github.com/xnimorz/masked-input)
JavaScript
2
star
22

e2

e2 (Emit-Event) is a custom event emitter.
JavaScript
1
star
23

BWar

Game BubblesWar
C++
1
star
24

FrontendConf-2019-materials

Материалы по докладу Hit Points вашего сервиса FrontendConf 2019
1
star
25

rplain

React container for only children rendering. Out of date
JavaScript
1
star