3
\$\begingroup\$

I have an event handler function which every time is triggered should clear a setTimeout() and set a new timeout.

Now the callback for the timeout has to take few arguments, for example the event object received by the handler.

And that's it. I did it, but I believe that my solution is not optimal, nor the cleanest:


let timer;

const handler = (event, type) => {

    clearTimeout(timer);

    if(type === 'CHANGE') timer = setTimeout(callback, 500, { argumentsForTheCallBack });
}

As you can see I have my timer as a global variable, even though I need it only in the event handler.

Is there a way to make the handler self-contained?

I am interested in ECMAScript solutions as well as solution using React.js syntax (in my original code the callback is a dispatcher for a useReducer hook)

\$\endgroup\$
3
\$\begingroup\$

Since you mention useReducer React hook I will assume you are using a functional component and thus can use other React hooks.

I suggest:

  1. Use a React ref to hold a reference to the timer (versus global variable).
  2. Use an useEffect hook to clear any running timeouts in the case the component unmounts before the timeout expires. Provide an empty dependency array so the effect callback is called only once. Here it is just to return the cleanup function to clear any running timeouts.

Code:

import { useEffect, useRef } from 'react';

...

// in component
const timerRef = useRef(null);

useEffect(() => {
  return () => clearTimeout(timerRef.current);
}, []);

const handler = (event, type) => {
  clearTimeout(timerRef.current);

  if (type === 'CHANGE') {
    timerRef.current = setTimeout(callback, 500, { argumentsForTheCallBack });
  }
}

Why provide cleanup function from useEffect?

If you attempt to update state of an unmounted component you'll get a React warning stating:

Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

It's just a warning though, so if you are ok with the warning in non-production builds and you know you don't actually have a memory leak (open sockets, connections, subscriptions, etc...) then you can likely safely ignore them.

In general you should strive to write code that doesn't generate warnings though, and this is a warning that is easily preventable.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ Thanks for the answer. I am fairly new to React and as far as I know this useEffect with an empty array as dependency should run only one time, when the app starts running. What am I missing? \$\endgroup\$ – Sheik Yerbouti Apr 24 at 13:41
  • 1
    \$\begingroup\$ @SheikYerbouti Yes, that is exactly the intention. I updated answer to provide this additional information for clarity and purpose. \$\endgroup\$ – Drew Reese Apr 24 at 17:43
  • 1
    \$\begingroup\$ @SheikYerbouti Added response to your follow-on question in an update in my answer. \$\endgroup\$ – Drew Reese Apr 24 at 18:02
  • 1
    \$\begingroup\$ Thank you very much for the explanation. Now I got that I should clear any asynchronous task, but wasn't the useEffect executed only once at the beginning? Is it automatically called again when the component unmounts? And if yes, is this true only for useEffects with an empty array as dependency? \$\endgroup\$ – Sheik Yerbouti Apr 24 at 18:11
  • 1
    \$\begingroup\$ @SheikYerbouti In this case, yes, the useEffect hook is called at the end of the initial render, and the effect cleanup will be called when the component unmounts. Generally, the cleanup function is called at the end of the render cycle before the component is rerendered (to clean up any effects from the previous render) or when the component is unmounting. The effect with empty dependency and returned cleanup function is roughly equivalent to the componentDidMount and componentWillUnmount lifecycle methods. See reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect. \$\endgroup\$ – Drew Reese Apr 24 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.