I'm building a custom hook that accepts a function. If the function will not change between re-renders/updates, where should I memoize the function?
OPTION 1
const useCustomHook = (callback) => {
const callbackRef = useRef();
callbackRef.current = callback;
// No effect, this will be re-created every time this hook is called
// because a new instance of callback is being passed
const callbackWrapper = useCallback(() => {
if (callbackRef.current) {
callbackRef.current();
}
}, [callbackRef]);
// use callbackWrapper
}
const Component = () => {
// New instance of passed callback will be created each time this component re-renders
useCustomHook(() => {
console.log(`I'm being passed to the hook`);
});
// ...
// ...
return <div></div>;
}
OPTION 2
const useCustomHook = (callback) => {
// Callback is already memoized
const callbackRef = useRef();
callbackRef.current = callback;
// use callbackRef
}
const Component = () => {
// Memoized function passed, but
// 1. Is this allowed?
// 2. Requires more effort by users of the hook
useCustomHook(useCallback(() => {
console.log(`I'm being passed to the hook`);
}, []));
// ...
// ...
return <div></div>;
}
Option 2 seems more valid but it requires users of the custom hook to first enclose their function in a useCallback()
hook. Is there an alternative way where users don't need to enclose the passed function in useCallback()
?
CodePudding user response:
I would probably use the useEffect
hook to "memoize" the callback
to the React ref.
const useCustomHook = (callback) => {
const callbackRef = useRef();
useEffect(() => {
// only update callback saved in ref if callback updates
callbackRef.current = callback;
}, [callback]);
// provide stable callback that always calls latest
// callback stored in ref
const callbackWrapper = useCallback(() => {
if (callbackRef.current) {
callbackRef.current();
}
}, []);
// use callbackWrapper
}
Or use useMemo
or useCallback
directly, but at this point you are essentially just redefining useMemo
or useCallback
in your custom hook. In this case, pass an additional dependency array argument.
const useCustomHook = (callback, deps) => {
const callbackWrapper = useMemo(() => callback, deps);
// use callbackWrapper
}
This being said, you will likely want to be in the habit of memoizing callbacks that are being passed down before they are passed, in order to guaranteed provided stable references. This way you won't need to go through the rigamarole of the weirdness of your custom hook.
CodePudding user response:
Another other way is to declare the callback outside of the component body ( but I don't recommend that, It can create some edge cases )
function cb () {
console.log(`I'm being passed to the hook`);
}
const Component = () => {
useCustomHook(cb);
// ...
// ...
return <div></div>;
}
memoize the function in you want on top level component body and then pass it to your custom hook like this:
const Component = () => {
// hooks should be called on top level function body
const cb = useCallback(() => {
console.log(`I'm being passed to the hook`);
}, []);
useCustomHook(cb);
// ...
// ...
return <div></div>;
}