fa19c4410f
After speaking with @timneutkens, this PR provides a smoother experience to users trying to migrate over to app without affecting users in pages. This PR adds a new export available from `next/compat/router` that exposes a `useRouter()` hook that can be used in both `app/` and `pages/`. It differs from `next/router` in that it does not throw an error when the pages router is not mounted, and instead has a return type of `NextRouter | null`. This allows developers to convert components to support running in both `app/` and `pages/` as they are transitioning over to `app/`. A component that before looked like this: ```tsx import { useRouter } from 'next/router'; const MyComponent = () => { const { isReady, query } = useRouter(); // ... }; ``` Will error when converted over to `next/compat/router`, as `null` cannot be destructured. Instead, developers will be able to take advantage of new hooks: ```tsx import { useEffect } from 'react'; import { useRouter } from 'next/compat/router'; import { useSearchParams } from 'next/navigation'; const MyComponent = () => { const router = useRouter() // may be null or a NextRouter instance const searchParams = useSearchParams() useEffect(() => { if (router && !router.isReady) { return } // In `app/`, searchParams will be ready immediately with the values, in // `pages/` it will be available after the router is ready. const search = searchParams.get('search') // ... }, [router, searchParams]) // ... } ``` This component will now work in both `pages/` and `app/`. When the component is no longer used in `pages/`, you can remove the references to the compat router: ```tsx import { useSearchParams } from 'next/navigation'; const MyComponent = () => { const searchParams = useSearchParams() // As this component is only used in `app/`, the compat router can be removed. const search = searchParams.get('search') // ... } ``` Note that as of Next.js 13, calling `useRouter` from `next/router` will throw an error when not mounted. This now includes an error page that can be used to assist developers. We hope to introduce a codemod that can convert instances of your `useRouter` from `next/router` to `next/compat/router` in the future. Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import React from 'react'
|
||
import { useRouter } from './router'
|
||
|
||
const nextjsRouteAnnouncerStyles: React.CSSProperties = {
|
||
border: 0,
|
||
clip: 'rect(0 0 0 0)',
|
||
height: '1px',
|
||
margin: '-1px',
|
||
overflow: 'hidden',
|
||
padding: 0,
|
||
position: 'absolute',
|
||
width: '1px',
|
||
|
||
// https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
|
||
whiteSpace: 'nowrap',
|
||
wordWrap: 'normal',
|
||
}
|
||
|
||
export const RouteAnnouncer = () => {
|
||
const { asPath } = useRouter()
|
||
const [routeAnnouncement, setRouteAnnouncement] = React.useState('')
|
||
|
||
// Only announce the path change, but not for the first load because screen
|
||
// reader will do that automatically.
|
||
const previouslyLoadedPath = React.useRef(asPath)
|
||
|
||
// Every time the path changes, announce the new page’s title following this
|
||
// priority: first the document title (from head), otherwise the first h1, or
|
||
// if none of these exist, then the pathname from the URL. This methodology is
|
||
// inspired by Marcy Sutton’s accessible client routing user testing. More
|
||
// information can be found here:
|
||
// https://www.gatsbyjs.com/blog/2019-07-11-user-testing-accessible-client-routing/
|
||
React.useEffect(
|
||
() => {
|
||
// If the path hasn't change, we do nothing.
|
||
if (previouslyLoadedPath.current === asPath) return
|
||
previouslyLoadedPath.current = asPath
|
||
|
||
if (document.title) {
|
||
setRouteAnnouncement(document.title)
|
||
} else {
|
||
const pageHeader = document.querySelector('h1')
|
||
const content = pageHeader?.innerText ?? pageHeader?.textContent
|
||
|
||
setRouteAnnouncement(content || asPath)
|
||
}
|
||
},
|
||
// TODO: switch to pathname + query object of dynamic route requirements
|
||
[asPath]
|
||
)
|
||
|
||
return (
|
||
<p
|
||
aria-live="assertive" // Make the announcement immediately.
|
||
id="__next-route-announcer__"
|
||
role="alert"
|
||
style={nextjsRouteAnnouncerStyles}
|
||
>
|
||
{routeAnnouncement}
|
||
</p>
|
||
)
|
||
}
|
||
|
||
export default RouteAnnouncer
|