Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: useThrottle useThrottleFn unmount issue in Next.js #2475

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

johnnywang1994
Copy link

@johnnywang1994 johnnywang1994 commented Feb 8, 2023

Description

  • Env: next 13.1.1 + Typescript
  • React strictMode cause useThrottle, useThrottleFn unmount bug

Situation:

  • As is: click div but nothing print out in console.
  • To be: print text in console.
import { FC, useState } from 'react';
import { useThrottleFn } from 'react-use';

const RefreshPageButton: FC = () => {
  const [value, setVal] = useState(0);

  useThrottleFn(() => {
    console.log('throttle triggered');
  }, 2000, [value]);

  return (
    <div onClick={() => setVal(value + 1)}>Just Test</div>
  );
};

export default RefreshPageButton;

after exploring the reason, I've found that because of the StrictMode, useThrottleFn will trigger as mount-unmount-mount in Next.js when first time rendering, which cause the timeoutCallback not triggered as expected and let the timeout ref value mistakenly remain exist.

Solution

import { useEffect, useRef, useState } from 'react';
import useUnmount from './useUnmount';

const useThrottleFn = <T, U extends any[]>(fn: (...args: U) => T, ms: number = 200, args: U) => {
  const [state, setState] = useState<T | null>(null);
  const timeout = useRef<ReturnType<typeof setTimeout>>();
  const nextArgs = useRef<U>();

  useEffect(() => {
    if (!timeout.current) {
      setState(fn(...args));
      const timeoutCallback = () => {
        if (nextArgs.current) {
          setState(fn(...nextArgs.current));
          nextArgs.current = undefined;
          timeout.current = setTimeout(timeoutCallback, ms);
        } else {
          timeout.current = undefined;
        }
      };
      timeout.current = setTimeout(timeoutCallback, ms);
    } else {
      nextArgs.current = args;
    }
  }, args);

  useUnmount(() => {
    timeout.current && clearTimeout(timeout.current);
    // reset timeout ref value to let `timeoutCallback` run as expected in StrictMode
    timeout.current = undefined;
  });

  return state;
};

export default useThrottleFn;

Type of change

  • Bug fix useThrottle, useThrottleFn timeout ref value remain exist after first unmount in StrictMode.

Checklist

  • Read the Contributing Guide
  • Perform a code self-review
  • Comment the code, particularly in hard-to-understand areas
  • Add documentation
  • Add hook's story at Storybook
  • Cover changes with tests
  • Ensure the test suite passes (yarn test)
  • Provide 100% tests coverage
  • Make sure code lints (yarn lint). Fix it with yarn lint:fix in case of failure.
  • Make sure types are fine (yarn lint:types).

@royvandewater
Copy link

Looks like this fixes #2343 and #2488. Am curious why this hasn't been merged yet.

@royriver23
Copy link

Agreed. It would be awesome to have this merged 🙏🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants