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
);
The useCallback hook will return a memoized version of the callback, and it'll only be changed if one of the dependencies has changed. You can also pass an empty array of dependencies. This will execute the function only once. If you don't pass an array, this will return a new value on every call.
Feel free to remove all useMemo and useCallbacks from the code if: they passed as attributes, directly or through a chain of dependencies, to DOM elements. they passed as props, directly or through a chain of dependencies, to a component that is not memoized.
React's useCallback hook offers a performance gain when passing the generated (memoized) function to a child component in order to avoid unnecessary re-renders.
You can use the useCallback Hook to preserve a function across re-renders. This will prevent unnecessary re-renders when a parent component recreates a function. By the end of this step, you'll be able to prevent re-renders using the useCallback Hook.
Short answer: use useMemo
instead of useCallback
, like so:
const filteredContacts = useMemo(() => filterContacts(contacts), [
value
]);
...
<ContactList contacts={filteredContacts} />
Why ? useCallback
memoizes the creation of a function. Meaning, the reference of the function will be the same, if the diffing parameters are the same. It will STILL be called everytime tho, and in your case, won't prevent any compute.
What you want is to only filter your contacts if value
changes. useMemo
remembers the last return value of your function, and will only re-run when the diffing parameters change. And they won't change more than once every 800ms, because you debounce it well.
PS: you could use useCallback
to prevent filterContacts
from being re-computed for no reason like so:
const filterContacts = useCallback(() => filterContactsByValue(value.toLowerCase(), [value]);
Even tho in your case, the performance gain is tiny.
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>
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With