react-spectrum: `usePress` / `useLink` is not compatible with NextJS's `next/link`
🐛 Bug Report
Because of NextJS’s next/link
component’s special behaviour, custom components using the usePress
(or useLink
) do not work the way I’d expect
🤔 Expected Behavior
When I use a component built with react-aria hooks as a child of next/link
, I expect successful client-side navigation to the destination
😯 Current Behavior
Depending on the usage, either I see an error being thrown, or I see successful server-side navigation (a full page reload)
This is the error I see (you’ll be able to see it too in the CodeSandbox example I’ve linked)
Unhandled Runtime Error
TypeError: can't access property "nodeName", e.currentTarget is undefined
linkClicked
node_modules/next/dist/client/link.js (39:27)
onClick
node_modules/next/dist/client/link.js (201:28)
triggerPressEnd
node_modules/@react-aria/interactions/dist/module.js (213:0)
onPointerUp
node_modules/@react-aria/interactions/dist/module.js (427:0)
💁 Possible Solution
React-aria’s PressEvent
could expose properties available on the native MouseEvent
. Howver, I’m not sure about the feasibility / consequences of this
🔦 Context
We use React-aria in our design system for building a Button
component. In our app (which uses NextJS), some buttons only take you to a different page on the app, so I treat them as links (that just happen to look like buttons)
In Next, the recommended way to use links is via their next/link
component. That component injects an onClick
prop to its first child, and this onClick
calls event.preventDefault
to be able to do client-side navigation (AKA navigation without a full page refresh)
I expect to be able to use my Button
this way:
import Link from 'next/link'
function TakeMeSomewhereNice() {
return (
<Link href="/somewhere" passHref>
<Button />
</Link>
)
}
However, this throws an error (you should be able to see this in the CodeSandbox I’ve linked below).
I understand this is quite a niche use-case, and I’m not even sure if react-aria can / should handle this. I can’t imagine where else this problem would pop up, so as far as I know, this issue is limited to next/link
.
I have investigated the issue and do have ways to get around this but the solution defeats the purpose of using react-aria in the first place, so it is not ideal. I’m happy to explain in detail exactly what’s going on if you’d like (in Next and react-aria), but I’m not sure if that’s relevant / required right now.
For some more context, here is Next’s next/link
component: https://github.com/vercel/next.js/blob/5e185fc5da227801d3f12724be3577f4a719aa69/packages/next/client/link.tsx
💻 Code Sample
Here is a minimal repro: https://codesandbox.io/s/next-link-react-aria-incompatibility-l0k0j?file=/pages/index.jsx
The idea is that navigating between the /
& /about
pages should not reload the page.
You’ll see that I have two components on each page. The Button
uses the usePress
hook, and the Link
uses the useLink
hook.
The Button
crashes, while the Link
navigates correctly, but the entire page is reloaded.
🌍 Your Environment
Software | Version(s) |
---|---|
@react-aria/interactions | 3.6.0 |
@react-aria/link | 3.1.4 |
Browser | Irrelevant |
Operating System | Irrelevant |
🧢 Your Company/Team
🕷 Tracking Issue (optional)
About this issue
- Original URL
- State: open
- Created 3 years ago
- Reactions: 4
- Comments: 15 (8 by maintainers)
I’ve read this in many many places by now, but it’s really not clear when
usePress
preventDefault the interaction.I’m trying to understand
usePress.ts
code and it seems all events doshouldPreventDefault
check before callingpreventDefault()
but the only one that does not do it is the actualonClick
: https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/interactions/src/usePress.ts#L263-L265I wonder if it should be:
cc: @devongovett
@snowystinger I’m facing the same issue trying to use react-router’s useLinkClickHandler for a Button implemented with
useButton
(very similar to React Spectrum Button). Your suggestion works to call the click handler in onPress, but the event is not defaultPrevented and therefore a full reload happens. Are you sure preventDefault is already called in most cases? From what I see, preventDefault for click events is called only when the element is disabled@snowystinger is there any news for this issue? I really like react-aria lib but to be able to use it in our projects we would need a support for nextjs link to work without a full reload. Thank you!
@snowystinger I should have been more clear. I meant button appearance but as an anchor element. Like how you can render a React Spectrum button as an anchor element via
elementType
. Here is a codesandbox trying to create aLinkButton
component inspired by the suggestion from react router docs.preventDefault
being a noop function, just prevents the runtime error, but doesn’t prevent the default reload behavior of course. We could even create auseLinkPressHandler
hook, similar touseLinkClickHandler
, but that would still require ability to call preventDefault on the original event.There is a solution if you’re using react-aria, you can preventDefault yourself. Here’s a codesandbox based on the one in the description https://codesandbox.io/s/next-link-react-aria-incompatibility-forked-seom13?file=/pages/about.jsx
Bringing it up with the team, I’ll reopen for now for more visibility.