useCancellableTimeout.ts 1.2 KB

12345678910111213141516171819202122232425262728293031323334353637
  1. import { useCallback, useEffect, useRef } from 'react';
  2. /**
  3. * setTimeout that cannot outlive the component that scheduled it.
  4. *
  5. * Modals here defer their own close by a second or more so the printer has
  6. * time to process the command that was just sent. A plain setTimeout for that
  7. * keeps a reference to setState and to the parent's onClose, and fires whether
  8. * or not the modal is still mounted — closing an already-dismissed dialog, or
  9. * throwing outright once the surrounding environment is gone ("window is not
  10. * defined" when a test's DOM is torn down before the timer fires).
  11. *
  12. * Returns a schedule function. Scheduling again replaces any pending timer, and
  13. * unmounting cancels it.
  14. */
  15. export function useCancellableTimeout() {
  16. const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
  17. const cancel = useCallback(() => {
  18. if (timer.current !== null) {
  19. clearTimeout(timer.current);
  20. timer.current = null;
  21. }
  22. }, []);
  23. const schedule = useCallback((fn: () => void, ms: number) => {
  24. cancel();
  25. timer.current = setTimeout(() => {
  26. timer.current = null;
  27. fn();
  28. }, ms);
  29. }, [cancel]);
  30. useEffect(() => cancel, [cancel]);
  31. return { schedule, cancel };
  32. }