Home > Back-end >  How to trigger useCallback, useMemo or useEffect on button click?
How to trigger useCallback, useMemo or useEffect on button click?

Time:07-26

I've heard that declaring a function in a react component to handle clicks should be done inside a useCallback hook to avoid recreating the function every render, specially if the function is somewhat complex, like this:

const handleClick = useCallback(()=>
{
  "...do a lot of calculations here that takes time"
},[]);    

<button onClick={handleClick}>
  Click me!
</button>

But I have a special case where I want the useCallback function to trigger when the button is clicked to set a state, this is what I want :

const [state, setState] = useState();

const handleClick = useCallback(()=>
{
  const newState = "...do a lot of calculations here that takes time"
  setState( newState )
},[ when button is clicked ]);

The problem is, how do i put the "when button is clicked" on the dependency of useCallback, and, as a matter of fact, any react hook that accepts a dependency list? I could do it with some dumb variable and separating the expensive calculations that take time from the onClick function, like bellow, but I don't think this is a good aproach since I'll be wasting memory:

const [state, setState] = useState();

const [dumb, setDumb] = useState(0);

useEffect(()=>
{
  const newState = "...do a lot of calculations here that takes time"
  setState( newState )
},[ dumb ]);

<button onClick=()=>setDumb(dumb 1)>
  Click me!
</button>

CodePudding user response:

I think you've got it a bit backwards. The useCallback hook is really only useful if the returned memoized function is passed down to descendant component via props. The benefit isn't that the function is heavy and some computation is optimized, but rather that the callback reference is stable and won't trigger an unnecessary rerender of a child component.

As for using a function as a button's onClick handler, it's the useCallback hook's return value that is passed to the button's onClick handler, not the other way around where you are trying to make the click event the dependency.

Example:

const clickHandler = useCallback(() => {
  // do some expensive computations
}, [...dependencies]);

...

<button type="button" onClick={clickHander}>Click Me</button>

If you aren't passing clickHandler as a prop to another React component there isn't much benefit to memoizing it.

The setDumb code example is a use case where using the useEffect hook would be more appropriate, the effect being to run the complex/heavy code when the dependency of the dumb state is updated.

It's simpler to just put the complex/heavy logic in a regular callback and enqueue a normal state update when it completes. Only use the useCallback if passing this function as a prop to another React component.

CodePudding user response:

The problem is, how do i put the "when button is clicked" on the dependency of useCallback

You don't - the dependencies should be exactly (and only) the state/props that are referenced inside the function.

For example, if the function was

() => {
  setCountState(countState   1);
  setToggledState(!toggledState);
}

then the dependencies array should be

[setCountState, countState, setToggledState, toggledState]

The ESLint rule exhaustive-deps helps you to automate this.

For your situation, it's perfectly fine to have the above as the only dependencies - you don't want a "dumb" placeholder value or any other dependency in the dependency array.


All that said:

declaring a function in a react component to handle clicks should be done inside a useCallback hook to avoid recreating the function every render, specially if the function is somewhat complex

This isn't really the case. useCallback isn't needed or useful in 99% of situations unless the function is being passed down to other components, in my experience - which doesn't appear to be the case here. (The function will be re-created every render regardless.)

useCallback shines when there are other components that use a function (especially components you don't know about), and you want to pass down as stable a reference as possible to them, to avoid unnecessary re-renders and hook calls.

CodePudding user response:

useCallback doesn't prevent the creation of the function. It only prevents you from having a new function reference every time you re-render. So with useCallback, the function is re-created every render, buy useCallback gives you back an old reference from a previous function if none of the listed dependencies have updated.

You want to create the click handler, add all the dependencies that it uses (setState, etc) and then use the handler on an event listener like a button's onClick.

const Comp = () => {
  const [state, setState] = useState([]);

  const clickHandler = useCallback((ev) => {
    setState(prevState => prevState.concat([/* something */])
  }, [setState]);

  return <button onClick={clickHandler}>Button</button>
}
  • Related