Repository: remix-run/react-router
File: packages/react-router-dom/index.tsx (TypeScript)

Code before:
 * you need to create custom `<Link>` components with the same click behavior we
 * use in our exported `<Link>`.
 */
export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
  to: To,
  {
    target,
    replace: replaceProp,
    state,
    preventScrollReset,
    relative,
    unstable_viewTransition,
  }: {
    target?: React.HTMLAttributeAnchorTarget;
    replace?: boolean;
    state?: any;
    preventScrollReset?: boolean;
    relative?: RelativeRoutingType;
    unstable_viewTransition?: boolean;
  } = {}
): (event: React.MouseEvent<E, MouseEvent>) => void {
  let navigate = useNavigate();
  let location = useLocation();
  let path = useResolvedPath(to, { relative });

  return React.useCallback(
    (event: React.MouseEvent<E, MouseEvent>) => {
      if (shouldProcessLinkClick(event, target)) {
        event.preventDefault();

        // If the URL hasn't changed, a regular <a> will do a replace instead of
        // a push, so do the same here unless the replace prop is explicitly set
        let replace =
          replaceProp !== undefined
            ? replaceProp
            : createPath(location) === createPath(path);

        navigate(to, {
          replace,
          state,
          preventScrollReset,
          relative,
          unstable_viewTransition,
        });
      }
    },
    [
      location,
      navigate,
      path,
      replaceProp,

Review comment (bug):
I _think_ we're OK using a ref here but I need to bang on some edge cases to be sure - all tests and such pass as-is which is reassuring.  The idea is that anytime a `fetcher.data` needs to update - a fetcher will have changed `state` which will be what triggers the re-render, and we don't need ` useState` version of `fetcherData`.

Code after:
  UseScrollRestoration = "useScrollRestoration",
}

// Internal hooks

function getDataRouterConsoleError(
  hookName: DataRouterHook | DataRouterStateHook
) {
  return `${hookName} must be used within a data router.  See https://reactrouter.com/routers/picking-a-router.`;
}

function useDataRouterContext(hookName: DataRouterHook) {
  let ctx = React.useContext(DataRouterContext);
  invariant(ctx, getDataRouterConsoleError(hookName));
  return ctx;
}

function useDataRouterState(hookName: DataRouterStateHook) {
  let state = React.useContext(DataRouterStateContext);
  invariant(state, getDataRouterConsoleError(hookName));
  return state;
}

function useFetcherDataLayer() {
  let fetcherRefs = React.useRef<Map<string, number>>(new Map());
  let fetcherData = React.useRef<Map<string, any>>(new Map());

  let registerFetcher = React.useCallback(
    (key: string) => {
      let count = fetcherRefs.current.get(key);
      if (count == null) {
        fetcherRefs.current.set(key, 1);
      } else {
        fetcherRefs.current.set(key, count + 1);
      }
    },
    [fetcherRefs]
  );

  let unregisterFetcher = React.useCallback(
    (key: string) => {
      let count = fetcherRefs.current.get(key);
      if (count == null || count <= 1) {
        fetcherRefs.current.delete(key);
        fetcherData.current.delete(key);
      } else {
        fetcherRefs.current.set(key, count - 1);
      }
    },
    [fetcherData, fetcherRefs]
  );