Description
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 an href
prop. This simply renders an a
link instead of a div
. 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
, the ref
's type is always assumed to be HTMLDivElement
. 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 to RouterProvider
. 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
- Allow for some sort of "slot" based solution that allows you to directly render a specific component, similar to
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. - Utility hooks with a higher level of abstraction. I think this would basically look like the lower level hooks in React Aria, but you get all of the props that React Aria would otherwise pass to its own components. This would allow consumers to decide how to resolve any conflicts and render whatever they'd like. This would be a sharp knife potentially but would allow us to drop down to a lower level without having to completely rewrite all of the component's functionality using the base React Aria hooks.
function MenuItemLink({ to, prefetch, ...props }) {
// this would give you all of the props that are
// passed into `ElementType` in your `MenuItemInner`
// component, but leaves rendering to the consumer.
const ref = React.useRef();
const menuItemProps = useMenuItemProps(ref, props);
return <Link prefetch={prefetch} to={to} {...menuItemProps} />;
}
🔦 Context + Examples
Assume here we're talking about rendering a link in MenuItem
.
When the Link
component in React Router resolves its to
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 a basename
. 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's current
status and supports functional props so you can switch on it.
And in Remix, it's pretty much impossible to support their Link
's prefetch
functionality without actually rendering their Link
.
🧢 Your Company/Team
Me personally
🕷 Tracking Issue
Couldn't find one!
Metadata
Metadata
Assignees
Type
Projects
Status
🔬 To Investigate / Verify