Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React useCallback with Parameter

Using React's useCallback hook is essentially just a wrapper around useMemo specialized for functions to avoid constantly creating new function instances within components' props. My question comes from when you need to pass an argued to the callback created from the memoization.

For instance, a callback created like so...

const Button: React.FunctionComponent = props => {     const onClick = React.useCallback(() => alert('Clicked!'), [])     return <button onClick={onClick}>{props.children}</button> } 

is a simple example of a memoized callback and required no external values passed into it in order to accomplish its job. However, if I want to create a generic memoized callback for a React.Dipatch<React.SetStateAction> function type, then it would require arguments...for example:

const Button: React.FunctionComponent = props => {     const [loading, setLoading] = React.useState(false)     const genericSetLoadingCb = React.useCallback((x: boolean) => () => setLoading(x), [])      return <button onClick={genericSetLoadingCb(!loading)}>{props.children}</button> } 

In my head, this seems like its the exact same as doing the following...

const Button: React.FunctionComponent = props => {     const [loading, setLoading] = React.useState(false)     return <button onClick={() => setLoading(!loading)}>{props.children}</button> } 

which would let defeat the purpose of memoizing the function because it would still be creating a new function on every render since genericSetLoadingCb(false) would just be returning a new function on each render as well.

Is this understanding correct, or does the pattern described with arguments still maintain the benefits of memoization?

like image 451
m_callens Avatar asked Apr 16 '20 16:04

m_callens


People also ask

Can useCallback take parameters?

The syntax As we can see, the useCallback React hook takes in an inline function and its dependencies as parameters and returns a memoized version of the function.

Is useCallback FN same as useMemo (( => FN?

useMemo is very similar to useCallback. It accepts a function and a list of dependencies, but the difference between useMemo and useCallback is that useMemo returns the memo-ized value returned by the passed function. It only recalculates the value when one of the dependencies changes.

What is the second parameter of useCallback?

The syntax of useCallback hook Similarly to useEffect hook, useCallback also accepts two parameters. The first parameter is the function you want to memoize. The second parameter is an array of dependencies. This array of dependencies specifies values React should watch.

Can useMemo have parameters?

React's useMemo hook enables us to memoize the result of the execution of a function with a given set of parameters. Next time the function is called with the same parameter, we can return the data that has been cached, rather than re-executing the entire function.


1 Answers

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 }),     []   ); 

genericCb can be useful 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 
like image 113
Giorgi Moniava Avatar answered Sep 30 '22 19:09

Giorgi Moniava