import {
HTMLAttributes,
MouseEvent,
forwardRef,
useCallback,
useEffect,
useRef,
} from 'react';
import { useNavigate, useRouteMatchesForPath } from './hooks.js';
import { useIsRouteTransitioning } from './TransitionIndicator.js';
export interface LinkProps
extends Omit, 'href'> {
to: string;
external?: boolean;
newTab?: boolean;
replace?: boolean;
skipTransition?: boolean;
state?: any;
/**
* Keep query string params intact when navigating
*/
preserveQuery?: boolean;
}
export const Link = forwardRef(function Link(
{
to: rawTo,
onClick,
external: forceExternal,
newTab,
replace,
skipTransition = false,
state,
preserveQuery,
...rest
},
ref,
) {
const to = resolve(rawTo);
const external = forceExternal ?? isExternal(to);
const transitioning = useIsRouteTransitioning();
const preserveScroll = rawTo.startsWith('?');
const wasClickedRef = useRef(false);
// preloading and router stuff isn't useful for external or newTab
const notRouterCompatible = external || newTab;
useEffect(() => {
if (notRouterCompatible) return;
const cleanups: Array<() => void> = [];
return () => {
// skip cleanup if the link was clicked
if (wasClickedRef.current) {
wasClickedRef.current = false;
return;
}
for (const cleanup of cleanups) {
cleanup();
}
};
}, [notRouterCompatible]);
const navigate = useNavigate();
const handleClick = useCallback(
function handleClick(event: MouseEvent) {
event.preventDefault();
wasClickedRef.current = true;
let destination = to;
if (preserveQuery) {
const currentQuery = new URLSearchParams(window.location.search);
const destinationQuery = new URLSearchParams(to.split('?')[1]);
currentQuery.forEach((value, key) => {
if (!destinationQuery.has(key)) {
destinationQuery.set(key, value);
}
});
destination = `${to.split('?')[0]}?${destinationQuery.toString()}`;
}
navigate(destination, {
replace,
skipTransition,
state,
preserveScroll,
});
onClick?.(event);
},
[
onClick,
replace,
state,
preserveQuery,
preserveScroll,
to,
navigate,
skipTransition,
],
);
const pathAtRenderTime =
typeof window !== 'undefined' ? window.location.pathname : '';
const newTabProps = newTab
? {
target: '_blank',
rel: 'noopener noreferrer',
}
: {};
return (
);
});
function isExternal(url: string = '') {
return /^(\w+):\/\//.test(url);
}
function resolve(path: string = '') {
if (path.startsWith('.')) {
return new URL(path, window.location.href).pathname;
}
if (path.startsWith('?')) {
return window.location.pathname + path;
}
return path;
}