Apolo
Apolo

Reputation: 4050

Custom React hook with function as arguments

I'm trying to write custom hooks but I'm facing an issue whenever I want function as arguments.

I want to write a useDebouce hook; From example around the web I ended up with this:

import { useCallback, useEffect } from 'react';
import debounce from 'lodash/debounce';

const useDebounce = (fn, time, options) => {
  useEffect(() => console.log('reset'), [fn, time, options]);
  return useCallback(debounce(fn, time, options), [fn, time, options]);
};

I want to reset whenever an argument change.

However if I use it like this:

const randomComponent = () => {
  const doSomething = useDebounce(() => console.log('test'), 500, {maxWait: 1000});
  // ...
};

Every time my component renders the function and object reference change (1st and 3rd arguments), which means I end up creating a new debounced function everytime. So the debounce behavior doesn't work.

What is the best way to deal with callback references changing at every render ?

Upvotes: 3

Views: 275

Answers (1)

Dennis Vash
Dennis Vash

Reputation: 53984

There is no way out, the user must make sure it has the same reference:

import { useCallback, useEffect } from 'react';
import debounce from 'lodash/debounce';

const useDebounce = (fn, time, options) => {
  useEffect(() => {
    // Don't forget to cancel the debounce
    return () => debounce(fn).cancel();
  }, [fn, time, options]);

  return useCallback(debounce(fn, time, options), [fn, time, options]);
};

const log = () => console.log('test');
const maxWait = { maxWait: 1000 };

// Outer scope holds the same reference
const randomComponent = () => {
  const doSomething = useDebounce(log, 500, maxWait);
};

// Or memoization, more usefull with state
const randomComponent = () => {
  const log = useCallback(() => console.log('test'), []);
  const maxWait = useMemo(() => ({ maxWait: 1000 }), []);
  const doSomething = useDebounce(log, 500, maxWait);
};

Also, if you don't want to deal with references, the user might provide a comparison function. May suggest trying another approach:

// Something like that (not tested)
const useDebounce = (fn, time, options, comp) => {
  const debounceRef = useRef(debounce(fn, time, options));

  if (comp(fn, time, options)) {
    debounce(debounceRef.current).cancel();
    debounceRef.current = debounce(fn, time, options);
  }

  return useCallback(debounceRef.current, []);
};

Upvotes: 1

Related Questions