React useCallback with Parameter
Motivation and Problem Statement
Let's consider following (similar to your genericSetLoadingCb
) higher order function genericCb
:
const genericCb = React.useCallback(
(param) => (e) => setState({ ...state, [param]: e.target.value }),
[]
);
Say we use it in the following situation where Input
is a memoized component created using React.memo:
<Input value={state.firstName} onChange={genericCb('firstName')} />
Since Input
is memoized component, we may want the function generated by genericCb('firstName')
to remain the same across re-renders, so that the memoized component doesn't re-render needlessly. Below we will see how to achieve this.
Solution
Now, the way we constructed genericCb
above is we ensured that it remains the same across renders (due to usage of useCallback
).
However, each time you call genericCb
to create a new function out of it like this:
genericCb("firstName")
The returned function will still be different on each render. To also ensure the returned function is memoized for some input, you should additionally use some memoizing approach:
import memoize from "fast-memoize";
....
const genericCb = React.useCallback(
memoize((param) => (e) => setState({ ...state, [param]: e.target.value })),
[]
);
Now if you call genericCb("firstName")
to generate a function, it will return same function on each render, provided "firstName"
also remains the same.
Remarks
As pointed out in the comments above solution using useCallback
seems to produce warning (it didn't in my project though):
React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead
It seems the warning is there because we didn't pass inline function to useCallback
. The solution I found to get rid of this warning based on this github thread is to use useMemo
to imitate useCallback
like this:
// Use this; this doesn't produce the warning anymore
const genericCb = React.useMemo(
() =>
memoize(
(param) => (e) => setState({ ...state, [param]: e.target.value })
),
[]
);
Simply using memoize without useCallback
(or useMemo
as in the update) wouldn't work, as on next render it would invoke memoize from fresh like this:
let memoized = memoize(fn)
memoized('foo', 3, 'bar')
memoized('foo', 3, 'bar') // cache hit
memoized = memoize(fn); // without useCallback (or useMemo) this would happen on next render
// Now the previous cache is lost