Skip to content

Commit

Permalink
fix(system): missing useHref logic (#2943)
Browse files Browse the repository at this point in the history
* chore(deps): update pnpm-lock.yaml

* fix(system): missing useHref logic

* chore(docs): restructure heading

* feat(docs): add useHref content to routing page

* fix(hooks): revise useAriaLink onClick logic

* chore(deps): bump @react-types/shared

* chore(deps): bump @react-types/shared

* fix(hooks): add missing parameters

* fix(pagination): add missing parameters

* feat(changeset): add missing router.open parameters due to rounter change

* fix(docs): onSelectionChange type

* refactor(changeset): revise changeset message

* chore(deps): lock deps

* chore(hooks): bump @react-types/shared to 3.23.0

* chore(changeset): remove this changeset since it is already in canary
  • Loading branch information
wingkwong authored Sep 10, 2024
1 parent 3ed05ab commit 4ac7674
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-eels-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/system": patch
---

Fix missing `useHref` logic (#2934)
93 changes: 74 additions & 19 deletions apps/docs/content/docs/guide/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ component configures all NextUI components within it to navigate using the clien
Set this up once in the root of your app, and any NextUI component with the href prop will automatically navigate
using your router.

### NextUIProvider Setup
## NextUIProvider Setup

The `NextUIProvider` accepts a prop called `navigate`. This should be set to a function received from your
router for performing a client side navigation programmatically. The following example shows the general
Expand All @@ -43,9 +43,9 @@ function App() {

<Spacer y={2} />

### Next.js
## Next.js

#### App Router
### App Router

Go to your `app/providers.tsx` or `app/providers.jsx` (create it if it doesn't exist) and add the
`useRouter` hook from `next/navigation`, it returns a router object that can be used to perform navigation.
Expand Down Expand Up @@ -95,15 +95,49 @@ export default function RootLayout({children}: { children: React.ReactNode }) {

> **Note**: Skip this step if you already set up the `NextUIProvider` in your app.
#### Add useHref (Optional)

If you are using the Next.js [basePath](https://nextjs.org/docs/app/api-reference/next-config-js/basePath) setting, you'll need to configure an environment variable to access it.

```js
// next.config.js
const basePath = '...';
const nextConfig = {
basePath,
env: {
BASE_PATH: basePath
}
};
```
Then, provide a custom `useHref` function to prepend it to the href for all links.

```tsx {9,12}
// app/providers.tsx
'use client'

import {NextUIProvider} from '@nextui-org/react';
import {useRouter} from 'next/navigation'

export function Providers({children}: { children: React.ReactNode }) {
const router = useRouter();
const useHref = (href: string) => process.env.BASE_PATH + href;

return (
<NextUIProvider navigate={router.push} useHref={useHref}>
{children}
</NextUIProvider>
)
}
```

</Steps>

#### Pages Router
### Pages Router

Go to pages`/_app.js` or `pages/_app.tsx` (create it if it doesn't exist) and add the`useRouter` hook
from `next/router`, it returns a router object that can be used to perform navigation.


```tsx {7}
```tsx {7,10}
// pages/_app.tsx
import type { AppProps } from 'next/app';
import {NextUIProvider} from '@nextui-org/react';
Expand All @@ -122,23 +156,47 @@ function MyApp({ Component, pageProps }: AppProps) {
export default MyApp;
```

### React Router
When using the [basePath](https://nextjs.org/docs/app/api-reference/next-config-js/basePath) configuration option, provide a `useHref` option to the router passed to Provider to prepend it to links automatically.

```tsx {8,11}
// pages/_app.tsx
import type { AppProps } from 'next/app';
import {NextUIProvider} from '@nextui-org/react';
import {useRouter} from 'next/router';

function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
const useHref = (href: string) => router.basePath + href;

return (
<NextUIProvider navigate={router.push} useHref={useHref}>
<Component {...pageProps} />
</NextUIProvider>
)
}

export default MyApp;
```

## React Router

The `useNavigate` hook from `react-router-dom` returns a `navigate` function that can be used to perform navigation.
The `useNavigate` hook from `react-router-dom` returns a `navigate` function that can be used to perform navigation.

The `useHref` hook can also be provided if you're using React Router's `basename` option. Ensure that the component that calls useNavigate and renders Provider is inside the router component (e.g. `BrowserRouter`) so that it has access to React Router's internal context. The React Router `<Routes>` element should also be defined inside `<NextUIProvider>` so that links inside the rendered routes have access to the router.

Go to the `App` file commonly called `App.jsx` or `App.tsx`, add the `useNavigate` hook and pass the
`navigate` function to the `NextUIProvider`:

```jsx {6,9}
// App.tsx or App.jsx
import {BrowserRouter, useNavigate} from 'react-router-dom';
import {BrowserRouter, useNavigate, useHref} from 'react-router-dom';
import {NextUIProvider} from '@nextui-org/react';

function App() {
const navigate = useNavigate();

return (
<NextUIProvider navigate={navigate}>
<NextUIProvider navigate={navigate} useHref={useHref}>
{/* Your app here... */}
<Routes>
<Route path="/" element={<HomePage />} />
Expand All @@ -164,17 +222,16 @@ component (e.g. `BrowserRouter`) so that it has access to React Router's interna
element should also be defined inside `NextUIProvider` so that links inside the rendered routes have access
to the router.

## Remix

### Remix

Remix uses React Router under the hood, so the same `useNavigate` hook described above also works in Remix
Remix uses React Router under the hood, so the same `useNavigate` and `useHref` hook described above also works in Remix
apps. `NextUIProvider` should be rendered at the `root` of each page that includes NextUI components, or in
`app/root.tsx` to add it to all pages. See the [Remix docs](https://remix.run/docs/en/main/file-conventions/root)
for more details.

```jsx {14}
// app/root.tsx
import {useNavigate, Outlet} from '@remix-run/react';
import {useNavigate, useHref, Outlet} from '@remix-run/react';
import {NextUIProvider} from '@nextui-org/react';

export default function App() {
Expand All @@ -186,7 +243,7 @@ export default function App() {
{/* ... */}
</head>
<body>
<NextUIProvider navigate={navigate}>
<NextUIProvider navigate={navigate} useHref={useHref}>
<Outlet />
</NextUIProvider>
{/* ... */}
Expand All @@ -196,8 +253,7 @@ export default function App() {
}
```


### TanStack
## TanStack

To use [TanStack Router](https://tanstack.com/router/latest) with NextUI, render NextUI's RouterProvider inside your root route. Use `router.navigate` in the `navigate` prop, and `router.buildLocation` in the `useHref` prop.

Expand All @@ -219,8 +275,7 @@ function RootRoute() {
}
```


### Usage examples
## Usage examples

Now that you have set up the `NextUIProvider` in your app, you can use the `href` prop in the `Tabs`,
`Listbox` and `Dropdown` items to navigate between pages.
Expand Down
16 changes: 15 additions & 1 deletion packages/core/system/src/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {ModalProviderProps} from "@react-aria/overlays";
import type {ProviderContextProps} from "./provider-context";
import type {Href} from "@react-types/shared";

import {I18nProvider, I18nProviderProps} from "@react-aria/i18n";
import {RouterProvider} from "@react-aria/utils";
Expand Down Expand Up @@ -31,11 +32,20 @@ export interface NextUIProviderProps
* Link, Menu, Tabs, Table, etc.
*/
navigate?: (path: string) => void;
/**
* Convert an `href` provided to a link component to a native `href`
* For example, a router might accept hrefs relative to a base path,
* or offer additional custom ways of specifying link destinations.
* The original href specified on the link is passed to the navigate function of the RouterProvider,
* and useHref is used to generate the full native href to put on the actual DOM element.
*/
useHref?: (href: Href) => string;
}

export const NextUIProvider: React.FC<NextUIProviderProps> = ({
children,
navigate,
useHref,
disableAnimation = false,
disableRipple = false,
skipFramerMotionAnimations = disableAnimation,
Expand All @@ -50,7 +60,11 @@ export const NextUIProvider: React.FC<NextUIProviderProps> = ({
let contents = children;

if (navigate) {
contents = <RouterProvider navigate={navigate}>{contents}</RouterProvider>;
contents = (
<RouterProvider navigate={navigate} useHref={useHref}>
{contents}
</RouterProvider>
);
}

const context = useMemo<ProviderContextProps>(() => {
Expand Down

0 comments on commit 4ac7674

Please sign in to comment.