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

Code before:
 * Returns the happy-path data from the nearest ancestor <Await /> value
 */
export function useAsyncValue(): unknown {
  let value = React.useContext(AwaitContext);
  return value?._data;
}

/**
 * Returns the error from the nearest ancestor <Await /> value
 */
export function useAsyncError(): unknown {
  let value = React.useContext(AwaitContext);
  return value?._error;
}

let blockerId = 0;

/**
 * Allow the application to block navigations within the SPA and present the
 * user a confirmation dialog to confirm the navigation.  Mostly used to avoid
 * using half-filled form data.  This does not handle hard-reloads or
 * cross-origin navigations.
 */
export function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker {
  let { router } = useDataRouterContext(DataRouterHook.UseBlocker);
  let state = useDataRouterState(DataRouterStateHook.UseBlocker);

  let [blockerKey, setBlockerKey] = React.useState("");
  let [blocker, setBlocker] = React.useState<Blocker>(IDLE_BLOCKER);
  let blockerFunction = React.useCallback<BlockerFunction>(
    (args) => {
      return typeof shouldBlock === "function"
        ? !!shouldBlock(args)
        : !!shouldBlock;
    },
    [shouldBlock]
  );

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

  return blockerKey && state.blockers.has(blockerKey)
    ? state.blockers.get(blockerKey)!
    : blocker;
}

/**

Review comment (performance):
Moved this increment into a side effect to be compliant with `StrictMode`.  Fetchers probably have a similar issue but they don't need to put anything like a blocker function into an internal router cache so it doesn't end up causing any user-facing issues. 

Code after:
/**
 * Returns the happy-path data from the nearest ancestor <Await /> value
 */
export function useAsyncValue(): unknown {
  let value = React.useContext(AwaitContext);
  return value?._data;
}

/**
 * Returns the error from the nearest ancestor <Await /> value
 */
export function useAsyncError(): unknown {
  let value = React.useContext(AwaitContext);
  return value?._error;
}

let blockerId = 0;

/**
 * Allow the application to block navigations within the SPA and present the
 * user a confirmation dialog to confirm the navigation.  Mostly used to avoid
 * using half-filled form data.  This does not handle hard-reloads or
 * cross-origin navigations.
 */
export function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker {
  let { router, basename } = useDataRouterContext(DataRouterHook.UseBlocker);
  let state = useDataRouterState(DataRouterStateHook.UseBlocker);

  let [blockerKey, setBlockerKey] = React.useState("");
  let [blocker, setBlocker] = React.useState<Blocker>(IDLE_BLOCKER);
  let blockerFunction = React.useCallback<BlockerFunction>(
    (arg) => {
      if (typeof shouldBlock !== "function") {
        return !!shouldBlock;
      }
      if (basename === "/") {
        return shouldBlock(arg);
      }

      // 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: {