Repository: remix-run/react-router
File: packages/react-router/lib/hooks.tsx (TypeScript)

Code before:

  // Cleanup on unmount
  React.useEffect(
    () => () => router.deleteBlocker(blockerKey),
    [router, blockerKey]
  );

  // Prefer the blocker from state since DataRouterContext is memoized so this
  // ensures we update on blocker state updates
  return state.blockers.get(blockerKey) || blocker;
}

/**
 * Stable version of useNavigate that is used when we are in the context of
 * a RouterProvider.
 */
function useNavigateStable(): NavigateFunction {
  let { router } = useDataRouterContext(DataRouterHook.UseNavigateStable);
  let id = useCurrentRouteId(DataRouterStateHook.UseNavigateStable);

  let activeRef = React.useRef(false);
  useIsomorphicLayoutEffect(() => {
    activeRef.current = true;
  });

  let navigate: NavigateFunction = React.useCallback(
    (to: To | number, options: NavigateOptions = {}) => {
      warning(activeRef.current, navigateEffectWarning);

      // Short circuit here since if this happens on first render the navigate
      // is useless because we haven't wired up our router subscriber yet
      if (!activeRef.current) return;

      if (typeof to === "number") {
        router.navigate(to);
      } else {
        router.navigate(to, { fromRouteId: id, ...options });
      }
    },
    [router, id]
  );

  return navigate;
}

const alreadyWarned: Record<string, boolean> = {};

function warningOnce(key: string, cond: boolean, message: string) {
  if (!cond && !alreadyWarned[key]) {
    alreadyWarned[key] = true;
    warning(false, message);

Review comment (suggestion):
I believe this introduced a useEffect loop since setBlockerKey is used in the dependency array and changed in the same effect.

Code after:
      }

      // If they provided us a function and we've got an active basename, strip
      // it from the locations we expose to the user to match the behavior of
      // useLocation
      let { currentLocation, nextLocation, historyAction } = arg;
      return shouldBlock({
        currentLocation: {
          ...currentLocation,
          pathname:
            stripBasename(currentLocation.pathname, basename) ||
            currentLocation.pathname,
        },
        nextLocation: {
          ...nextLocation,
          pathname:
            stripBasename(nextLocation.pathname, basename) ||
            nextLocation.pathname,
        },
        historyAction,
      });
    },
    [basename, shouldBlock]
  );

  React.useEffect(() => {
    let key = String(++blockerId);
    setBlocker(router.getBlocker(key, blockerFunction));
    setBlockerKey(key);
    return () => router.deleteBlocker(key);
  }, [router, setBlocker, setBlockerKey, blockerFunction]);

  // Prefer the blocker from state since DataRouterContext is memoized so this
  // ensures we update on blocker state updates
  return blockerKey && state.blockers.has(blockerKey)
    ? state.blockers.get(blockerKey)!
    : blocker;
}

/**
 * Stable version of useNavigate that is used when we are in the context of
 * a RouterProvider.
 */
function useNavigateStable(): NavigateFunction {
  let { router } = useDataRouterContext(DataRouterHook.UseNavigateStable);
  let id = useCurrentRouteId(DataRouterStateHook.UseNavigateStable);

  let activeRef = React.useRef(false);
  useIsomorphicLayoutEffect(() => {
    activeRef.current = true;
  });