Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion packages/next/src/client/app-dir/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,36 @@ function formatStringOrUrl(urlObjOrString: UrlObject | string): string {
return formatUrl(urlObjOrString)
}

/**
* Returns the ref of a React element handling differences between React 19 and older versions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this link solely used in App Router where we don't need to handle React 18?

* It will throw runtime error if the element is not a valid React element.
*
* The source is a copy and paste of https://github.com/radix-ui/primitives/blob/6e75e117977c9e6ffa939e6951a707f16ba0f95e/packages/react/presence/src/presence.tsx#L173
* It's also duplicated in the Pages Router source.
* @param element React.ReactElement
* @returns React.Ref<any> | undefined
*/
function getReactElementRef(
element: React.ReactElement<{ ref?: React.Ref<unknown> }>
): React.Ref<any> | undefined {
// React <=18 in DEV
let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get
let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
if (mayWarn) {
return (element as any).ref
}

// React 19 in DEV
getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get
mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
if (mayWarn) {
return element.props.ref
}

// Not DEV
return element.props.ref || (element as any).ref
}

/**
* A React component that extends the HTML `<a>` element to provide
* [prefetching](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching)
Expand Down Expand Up @@ -546,7 +576,7 @@ export default function LinkComponent(
}

const childRef: any = legacyBehavior
? child && typeof child === 'object' && child.ref
? getReactElementRef(child)
: forwardedRef

// Use a callback ref to attach an IntersectionObserver to the anchor tag on
Expand Down
32 changes: 31 additions & 1 deletion packages/next/src/client/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,36 @@ function formatStringOrUrl(urlObjOrString: UrlObject | string): string {
return formatUrl(urlObjOrString)
}

/**
* Returns the ref of a React element handling differences between React 19 and older versions.
* It will throw runtime error if the element is not a valid React element.
*
* The source is a copy and paste of https://github.com/radix-ui/primitives/blob/6e75e117977c9e6ffa939e6951a707f16ba0f95e/packages/react/presence/src/presence.tsx#L173
* It's also duplicated in the App Router source.
* @param element React.ReactElement
* @returns React.Ref<any> | undefined
*/
function getReactElementRef(
element: React.ReactElement<{ ref?: React.Ref<unknown> }>
): React.Ref<any> | undefined {
// React <=18 in DEV
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just shortcut to the deprecated version in prod? Renders are a hotpath so we should be as nimble as possible. The goal here is to avoid the dev-only warning, no?

let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get
let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
if (mayWarn) {
return (element as any).ref
}

// React 19 in DEV
getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get
mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
if (mayWarn) {
return element.props.ref
}

// Not DEV
return element.props.ref || (element as any).ref
}

/**
* A React component that extends the HTML `<a>` element to provide [prefetching](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching)
* and client-side navigation between routes.
Expand Down Expand Up @@ -500,7 +530,7 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
}

const childRef: any = legacyBehavior
? child && typeof child === 'object' && child.ref
? getReactElementRef(child)
: forwardedRef

const [setIntersectionRef, isVisible, resetVisible] = useIntersection({
Expand Down
Loading