mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
# `useLinkStatus` Hook
The `useLinkStatus` hook is primarily useful when:
- Links have `prefetch={false}` set
- Navigation occurs before prefetching has completed
- When React decides to [skip the loading
state](https://react.dev/reference/react/useTransition#preventing-unwanted-loading-indicators)
and https://github.com/vercel/next.js/issues/69625
It provides loading state feedback during these navigation transitions,
preventing pages from appearing "frozen" while new content loads.
## Usage
`useLinkStatus` can be called from any descendant component of `Link`
and it returns `{ pending: true }` before history has updated. After the
URL updates, it returns `{ pending: false }`.
```jsx
// When prefetching is disabled
<Link href="/dashboard/reports" prefetch={false}>
Reports <LoadingIndicator />
</Link>
function LoadingIndicator() {
const { pending } = useLinkStatus();
return pending ? <Spinner /> : null;
}
```
## Notes
- If the link has been prefetched, pending change will be skipped and
remain `{ pending: false }`.
- If `useLinkStatus` is not under any `Link`, it will always return `{
pending: false }`.
- `useLinkStatus` currently does not support Pages Router, so it will
always return `{ pending: false }` in Pages Router.
- When you click multiple links in a short period, only the last link's
pending state is shown
---------
Co-authored-by: Andrew Clark <[email protected]>
121 lines
3.2 KiB
TypeScript
121 lines
3.2 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import Link from 'next/link'
|
|
import { useLinkStatus } from 'next/link'
|
|
import { navigateByServerAction } from './server-action'
|
|
import { useRouter } from 'next/navigation'
|
|
|
|
export default function NavBar() {
|
|
const postIds = Array.from({ length: 5 }, (_, i) => i + 1)
|
|
const router = useRouter()
|
|
|
|
return (
|
|
<nav data-testid="navbar">
|
|
<ul style={{ display: 'flex', listStyle: 'none', gap: '10px' }}>
|
|
<li>
|
|
<Link
|
|
prefetch={false}
|
|
href="/"
|
|
id="home-link"
|
|
data-testid="home-link"
|
|
>
|
|
Home <LoadingIndicator id="home" />
|
|
</Link>
|
|
<button
|
|
id="server-action-home-btn"
|
|
data-testid="server-action-home-btn"
|
|
onClick={() => navigateByServerAction(`/`)}
|
|
>
|
|
Navigate by server action to home
|
|
</button>
|
|
</li>
|
|
{postIds.map((id) => (
|
|
<li key={id}>
|
|
<Link
|
|
prefetch={false}
|
|
href={`/post/${id}`}
|
|
id={`post-${id}-link`}
|
|
data-testid={`post-${id}-link`}
|
|
>
|
|
Post {id} <LoadingIndicator id={`post-${id}`} />
|
|
</Link>
|
|
<button
|
|
id={`server-action-${id}-btn`}
|
|
data-testid={`server-action-${id}-btn`}
|
|
onClick={() => navigateByServerAction(`/post/${id}`)}
|
|
>
|
|
Navigate by server action to {id}
|
|
</button>
|
|
<button
|
|
id={`router-push-${id}-btn`}
|
|
data-testid={`router-push-${id}-btn`}
|
|
onClick={() => router.push(`/post/${id}`)}
|
|
>
|
|
Navigate by Router.push to {id}
|
|
</button>
|
|
</li>
|
|
))}
|
|
|
|
<button
|
|
id="enable-debug-btn"
|
|
data-testid="enable-debug-btn"
|
|
onClick={() => {
|
|
const urlSearchParams = new URLSearchParams(window.location.search)
|
|
urlSearchParams.set('debug', '1')
|
|
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
|
|
}}
|
|
>
|
|
Enable Debug Mode
|
|
</button>
|
|
|
|
<button
|
|
id="disable-debug-btn"
|
|
data-testid="disable-debug-btn"
|
|
onClick={() => {
|
|
const urlSearchParams = new URLSearchParams(window.location.search)
|
|
urlSearchParams.delete('debug')
|
|
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
|
|
}}
|
|
>
|
|
Disable Debug Mode
|
|
</button>
|
|
|
|
<LinkThatChangesHref />
|
|
</ul>
|
|
</nav>
|
|
)
|
|
}
|
|
|
|
const LoadingIndicator = ({ id }: { id: string }) => {
|
|
const { pending } = useLinkStatus()
|
|
|
|
if (pending) {
|
|
return (
|
|
<div id={`${id}-loading`} data-testid={`${id}-loading`}>
|
|
(Loading)
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
const LinkThatChangesHref = () => {
|
|
const [postId, setPostId] = React.useState(6)
|
|
|
|
return (
|
|
<div>
|
|
<Link href={`/post/${postId}`} data-testid="changing-link">
|
|
Link to post {postId} <LoadingIndicator id={`post-${postId}`} />
|
|
</Link>
|
|
<button
|
|
onClick={() => setPostId(postId + 1)}
|
|
data-testid="increment-link-btn"
|
|
>
|
|
Change link target
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|