-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React Aria Components: rendering as custom components #5476
Comments
Pasting what I said on Twitter for posterity:
The ideal solution in my view for this case in particular would be if Remix exported their internal usePrefetchBehavior hook, or something like it. Then you could just do something like this: function MyMenuItem({prefetch, ...props}) {
let [ref, prefetchProps] = usePrefetch(ref);
return <ReactAriaMenuItem {...props} {...prefetchProps} ref={ref} />;
} This separation of behavior from elements makes sense especially because pre-fetching has nothing to do with the underlying element that gets rendered. Sometimes we actually have to render a different element than a native Aside from this one in particular, I think we are planning on adding some additional options to pass to the router's navigate function (e.g. |
I've been struggling to integrate React Router with React Aria Components and agree with the points raised in this issue. I have encountered most of the examples given and they are all quite common use-cases. Another issue I have come up against is working with different kinds of router e.g. hash router or memory router. With a hash router, for example, the RAC Ultimately, I think the way client side routing works in React Aria needs a bit of a rethink. Simply providing the
|
Current plan for router props is to have a single object that gets passed through to the underlying router, rather than individual props. That way it isn't specific to one router and you don't need to wait when new props are added by your framework. <MenuItem href="..." routerOptions={{preventScrollReset: true}} /> There is also a way to configure TypeScript to support autocomplete on these that we'll provide docs for. Something like NavLink I think should be handled by React Aria instead. For example, depending on the component it's used within, the accessibility properties should be different (ie The The prefetching one is definitely more complex because it needs actual behavior from the router link, not just settings for the native URL. Somehow we need to separate the individual behaviors from the rendering so that React Aria can do the event handling, and delegate the routing. Without the router separating this out into a hook, it's a bit of a challenge. |
This sounds like a good solution and would help a lot.
Yes, I think resolving back to the router URL is the important bit. How would you suggest using React Aria links so that you can provide relative URLs in the same way that you can with the routing library. For React Router, would it be to wrap the components that have an It seems like adding more documentation for how to integrate deeper router functionality with React Aria would be really useful. This may also highlight what can already be achieved in a straightforward way and what needs changes at the library level. Thanks for thinking about this. |
Just a thought about resolving the URLs. What if the import { RouterProvider } from 'react-aria-components';
import { useNavigate, useHref } from 'react-router-dom';
function App() {
const navigate = useNavigate()
return (
<RouterProvider navigate={navigate} useHref={useHref}>
{/* ... */}
</RouterProvider>
);
} It's a bit unusual to pass a hook as a prop like this but it would resolve the issues with relative paths, base paths and hash routers. |
I like that idea! Do you know if next.js has a similar API to resolve URLs? I couldn't find one in their docs. |
I don't think they do. It's worth considering TanStack Router too as the usage will likely grow now that it's reached 1.0 (https://tanstack.com/router/v1). That has the added challenge of providing the same level of type inference via the React Aria link components. It's hard to think of a single approach that will integrate fully with different routing libraries. |
FYI I implemented the |
To not wrap RouterProvider, do we have a chance just use from react router useNavigate and Link component from react-spectrum? As far as I see, if we do, then in this case we will have reloading and you can't prevent this behavior using onPress |
Maybe I'm doing something wrong but relative paths are not resolved correctly using react-router. I see the correct link when hovering the tab but when I click it brings me to the wrong path (relative to the root). For example I have a route: From the path I see the correct link on the element, but when I click it navigates to If in the same component I insert a I have used the |
You are not doing anything wrong. react-router links call the Aside from not using relative paths where possible, the only workaround I've found so far is using the |
Thanks @theMosaad I will try it out! |
I'm also struggling with integrating React Aria's links in Next.js. The documentation suggests using But you will lose all the nice prefetching behavior that the Next.js Features like |
Reiterating my prior comment, the problem is that the behavior of the Link component is not right in some cases. For example, links in tables and grids cannot be rendered as Prefetching behavior really needs to be separated from link behavior so it can be applied independently from how the link is rendered or triggered. Perhaps there's a way to integrate that into RouterProvider or maybe a separate wrapper component to make this easier. For now you could do something like this to programmatically prefetch on hover or focus: let router = useRouter();
<MenuItem
href="..."
onHoverStart={() => router.prefetch("...")}
onFocus={() => router.prefetch("...")} /> |
Thank you for providing an alternative solution. But unfortunately you also need to re-implement the prefetch behavior when the element becomes visible: https://github.com/vercel/next.js/blob/d62627cecfcfcf6f03362be3be369725170a3876/packages/next/src/client/link.tsx#L560-L596. It's not a gargantuan task, but it's really annoying and trial-error-prone. I feel like complementing the |
I think this should be (at least) well documented in https://react-spectrum.adobe.com/react-aria/routing.html#routerprovider. Currently the solutions is actually buggy, as the urls are showing properly, yet the navigation itself is wrong (treating relative paths as absolute navigations) - this is probably going to be encountered by many different users of We had to implement a wrapper that makes sure our relative hrefs (sometimes [unfortunately] we're using them) work properly. The wrapper is based on |
I just realized that React Spectrum has I'm baffled why this behavior isn't included for React Aria. The only way to mimic this behavior is to drop down to raw hooks and recreate the entirety of Trying to manage complex composition without this has been really, really frustrating, to the point that I've been reconsidering going back to Radix. Is there any chance that this could change in the future? |
This was a mistake from a long time ago that we have since reverted in our new version based on React Aria Components. Instead, we simply have two components: Button and LinkButton (which uses the RAC Link component with the same styles applied as Button).
Pretty sure it's explained if you read the thread here.
What are you trying to accomplish that you cannot achieve at the moment? Are there examples other than prefetching? |
After banging my head a bit, I decided to mimic the import { Button as AriaButton, Link as AriaLink } from "react-aria-components";
import type { ButtonProps as AriaButtonProps } from "react-aria-components";
import { forwardRef } from "react";
import type { LinkDOMProps } from "@react-types/shared";
export interface ButtonProps extends AriaButtonProps, LinkDOMProps {}
export const Button = forwardRef<React.ElementRef<typeof AriaButton>, ButtonProps>(
(props, ref) => {
const Element: React.ElementType =
props.href !== undefined ? AriaLink : AriaButton;
return <Element ref={ref} {...props} />;
},
);
Button.displayName = "Button"; The
My usecase is that I'm writing a collection of button components, and I want it make it so that you can transparently use any of them as links. The |
Here is a use case not supported as far as I know: My suggestion is to have an API like this:
Note: As far as I know this would be non-breaking and works fine with typescript because if
We are just giving the developer back some more control. Yes, this gives some responsibility to the developer, but it prevents the developer having to fight RAC, or potentially quit it over other headless libraries that do support customization the element. @devongovett You mentioned a couple times one could fallback to Maybe, being able to control the root element of a component removes the need to having to copy an internal RAC component, as you would be able to overwrite/customize any props passed to the element (just like you can with Somewhat related is an issue I ran into where I couldn't control the rendering part: |
Provide a general summary of the feature here
We should have a simpler way to render components other than what is returned from the React Aria component by default. This would be either some sort of polymorphic component rendering or a higher level hook.
In the
MenuItem
component (and possibly some others), the current way to render a link is to pass anhref
prop. This simply renders ana
link instead of adiv
. For a library like React Aria, which is typically used as a primitive to create design-system level components, this is quite limiting. There should be some API for rendering another component without dropping down to the lowest-level APIs with React Aria hooks.🤔 Expected Behavior?
TBD (exact behavior depends on the solution)
😯 Current Behavior
Currently I can't get the functionality of a component like
MenuItem
without either rendering that component or recreating it entirely with the much lower-level React Aria hooks.There are a few challenges with the current implementation:
TypeScript. Particularly when using
forwardRef
, theref
's type is always assumed to beHTMLDivElement
. This is a common theme in pretty much every component lib that embraces the "polymorphic" component pattern, and it's difficult to solve without introducing a number of other tradeoffs.Custom link components. React Aria basically assumes you either want a regular
a
for external links or you simply want to navigate using your app's client-side router, in which case it uses the navigator provided toRouterProvider
. I think this leaves a lot to be desired, as router and/or framework link components often bake in other features we lose if rendered in the context of a RA component.💁 Possible Solution
asChild
in Radix UI. This is probably not a great one for React Aria, since its interaction event props (onPress
and family) heavily abstract and hide the underlying DOM props which a lot of components (including RR links) rely on. Leaving React Aria to decide how to resolve likely conflicts is probably going to be a mess.🔦 Context + Examples
Assume here we're talking about rendering a link in
MenuItem
.When the
Link
component in React Router resolves itsto
prop it can determine whether or not a URL value is internal (for client-side routing) or external (leave it to the browser). To do this well it needs to use internal context for apps with abasename
. Recreating this behavior requires that you import non-public context modules and dupe their validation code, which isn't ideal.Another example in React Router is
NavLink
. This component also relies on internal context to determine the link'scurrent
status and supports functional props so you can switch on it.And in Remix, it's pretty much impossible to support their
Link
'sprefetch
functionality without actually rendering theirLink
.🧢 Your Company/Team
Me personally
🕷 Tracking Issue
Couldn't find one!
The text was updated successfully, but these errors were encountered: