reactjsreact-hooksreact-component

React: Is it okay if useCallback returns a value, or is this a bad pattern?


I have a function called filterContactsByValue. It is curried and takes in a value and a list of contacts and then filters the list based on the value and returns the (new) filtered list.

Since the list is often large (10.000+ entries), the web app should run on smartphones and the filter takes into account many values, I want to optimize the computing resources. Therefore I use useDebounce to not compute unnecessarily.

I also used useCallback like this to memoize the computation of the filteredContacts:

function FilteredContacts({contacts}) {
  const [filterParam, setFilterParam] = useState('');
  const [value] = useDebounce(filterParam, 800);
  const filterContacts = filterContactsByValue(value.toLowerCase());

  // Is this okay? 🤔 ...
  const getFilteredContacts = useCallback(() => filterContacts(contacts), [
    value
  ]);

  return (
    <div className="main">
      <SearchBar
        value={filterParam}
        onChangeText={setFilterParam}
      />
      // ... and then this? 🧐
      <ContactList contacts={getFilteredContacts()} />
    </div>
  );
}

I was wondering whether this is okay, or if returning values like this is bad practice. If it is bad, why and how would you improve it?

Edit: The filterContactsByValue function:

import { any, filter, includes, map, pick, pipe, toLower, values } from 'ramda';
import { stringFields } from './config/constants';

const contactIncludesValue = value =>
  pipe(
    pick(stringFields),
    map(toLower),
    values,
    any(includes(value))
  );

const filterContactsByValue = pipe(
  contactIncludesValue,
  filter
);

Solution

  • According to https://github.com/xnimorz/use-debounce you already have useDebouncedCallback hook.

    const getFilteredContacts = useDebouncedCallback(
        () => filterContactsByValue(value.toLowerCase()),
        800,
        [value]
      );
    

    You can also use lodash's debounce or throttle (when you have lodash in your project), but as @skyboyer mentioned you may end with out-of-date callback version(s) will be run after appropriate delay

    export {debounce} from 'lodash'; 
    
    const getFilteredContacts = useCallback(
        debounce(() => filterContactsByValue(value.toLowerCase()), 1000),
        [value]
    );
    
    

    but useMemo will be better option, because you don't really want function execution in your render method

    const FilteredContacts = ({contacts}) => {
        const [filterParam, setFilterParam] = useState('');
        const [value] = useDebounce(filterParam, 800);
        const contactsFilter = useMemo(
            () => filterContactsByValue(value.toLowerCase()),
            [value]
        );
        const filteredContacts = useMemo(
            () => contactsFilter(contacts), 
            [value, contacts]
        );
    
        return (
            <div className="main">
                <SearchBar
                    value={filterParam}
                    onChangeText={setFilterParam}
                />
                <ContactList contacts={filteredContacts} />
            </div>
        );
    }