Last active
May 8, 2024 20:02
-
-
Save Talor-A/edb3797cdaa0ccd18efda997c003fef3 to your computer and use it in GitHub Desktop.
connect react-aria-components with next router
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function MyMenu() { | |
return ( | |
<Menu> | |
<MenuItem | |
href={{ | |
pathname: '/about', | |
query: { name: 'test' }, | |
}}> | |
About Page | |
</MenuItem> | |
</Menu> | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { Url } from 'next/dist/shared/lib/router/router'; | |
import type { LinkProps as NextLinkProps } from 'next/link'; | |
import { useRouter } from 'next/router'; | |
import React from 'react'; | |
import { useCallback } from 'react'; | |
import { RouterProvider, type LinkProps } from 'react-aria-components'; | |
/** | |
* copied from next/router.d.ts | |
*/ | |
interface TransitionOptions { | |
shallow?: boolean; | |
locale?: string | false; | |
scroll?: boolean; | |
unstable_skipClientCache?: boolean; | |
} | |
interface NextRouterOptions extends TransitionOptions { | |
as?: Url; | |
/** allow passing href as an object. this will override the path passed to the `href` prop of the component. */ | |
href?: Url; | |
} | |
declare module 'react-aria-components' { | |
interface RouterConfig { | |
/** | |
* we're using interface merging to set `routerOptions` to accept props that `next/router` wants. | |
* | |
* this allows every `routerOptions` prop on a component from `react-aria-components` to accept | |
* the right next.js props with typesafety. | |
*/ | |
routerOptions: NextRouterOptions; | |
} | |
} | |
interface NextRouterProviderProps { | |
children: React.ReactNode; | |
} | |
type Navigate = ( | |
path: string, | |
routerOptions: NextRouterOptions | undefined, | |
) => void; | |
export function NextRouterProvider(props: NextRouterProviderProps) { | |
const { push } = useRouter(); | |
const navigate = useCallback<Navigate>( | |
/** | |
* path is the `href` prop that's passed to the component, and is always a string. | |
* | |
* however, we'll usually expect `nextRouterPropsToReactAriaProps` to have set `href` for us, | |
* so this fallback will only come into play if you're not using that helper. | |
*/ | |
(path, { as, href, ...options } = {}) => { | |
push(href || path, as, options); | |
}, | |
[push], | |
); | |
return <RouterProvider navigate={navigate}>{props.children}</RouterProvider>; | |
} | |
/** | |
* react-aria exposes a `href:string` prop and a `routerOptions` catchall prop on | |
* many of its components like `Link`, `MenuItem`, etc. | |
* | |
* it's more convenient to pass `href` / `as` / `shallow` as you would on a | |
* regular `next/link` component, and define `href` as a `Url` object if we want to. | |
* | |
* this interface defines the props from `next/link` that we want to proxy over to the | |
* right place to be used by stuff from `react-aria-components`. | |
*/ | |
export interface ReactAriaNextLinkProps | |
extends Partial<Pick<NextLinkProps, 'as' | 'href' | 'shallow' | 'scroll'>> {} | |
type RequireExplicitlyUndefined<T> = { | |
[P in keyof Required<T>]: T[P] | undefined; | |
}; | |
/** | |
* react-aria exposes a `href:string` prop and a `routerOptions` catchall prop on | |
* many of its components like `Link`, `MenuItem`, etc. | |
* | |
* it's more convenient to pass `href` / `as` / `shallow` as you would on a | |
* regular `next/link` component, and define `href` as a `Url` object if we want to. | |
* | |
* this function maps the props that are more ergonomic to use over to the right | |
* props that components from `react-aria-components` expect. | |
* | |
* when we create a wrapper around a `react-aria-components` component, we should use | |
* this function inside our wrapper. | |
*/ | |
export const nextRouterPropsToReactAriaProps = ( | |
props: RequireExplicitlyUndefined<ReactAriaNextLinkProps>, | |
): Pick<LinkProps, 'href' | 'routerOptions'> => { | |
const { as: asProp, href, shallow, scroll } = props; | |
if (!href) { | |
return {}; | |
} | |
return { | |
href: (asProp || href).toString(), | |
routerOptions: { | |
as: asProp, | |
href, | |
shallow, | |
scroll, | |
}, | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment