Added flag to identify shallow router events (#19802)
This PR is an implementation of RFC #13276 (more specifically @banerjeesouvik's [suggestion](https://github.com/vercel/next.js/discussions/13276#discussioncomment-17874)) which solves #3322 by adding an extra parameter to the router events that'll allow you to see whether or not the route is shallow. This is a recreation of PR #13243.
This commit is contained in:
parent
0970873bb7
commit
25cb43a73b
4 changed files with 71 additions and 37 deletions
|
@ -310,13 +310,13 @@ export default function Page() {
|
|||
|
||||
You can listen to different events happening inside the Next.js Router. Here's a list of supported events:
|
||||
|
||||
- `routeChangeStart(url)` - Fires when a route starts to change
|
||||
- `routeChangeComplete(url)` - Fires when a route changed completely
|
||||
- `routeChangeError(err, url)` - Fires when there's an error when changing routes, or a route load is cancelled
|
||||
- `routeChangeStart(url, { shallow })` - Fires when a route starts to change
|
||||
- `routeChangeComplete(url, { shallow })` - Fires when a route changed completely
|
||||
- `routeChangeError(err, url, { shallow })` - Fires when there's an error when changing routes, or a route load is cancelled
|
||||
- `err.cancelled` - Indicates if the navigation was cancelled
|
||||
- `beforeHistoryChange(url)` - Fires just before changing the browser's history
|
||||
- `hashChangeStart(url)` - Fires when the hash will change but not the page
|
||||
- `hashChangeComplete(url)` - Fires when the hash has changed but not the page
|
||||
- `beforeHistoryChange(url, { shallow })` - Fires just before changing the browser's history
|
||||
- `hashChangeStart(url, { shallow })` - Fires when the hash will change but not the page
|
||||
- `hashChangeComplete(url, { shallow })` - Fires when the hash has changed but not the page
|
||||
|
||||
> **Note:** Here `url` is the URL shown in the browser, including the [`basePath`](/docs/api-reference/next.config.js/basepath.md).
|
||||
|
||||
|
@ -332,8 +332,12 @@ export default function MyApp({ Component, pageProps }) {
|
|||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url) => {
|
||||
console.log('App is changing to: ', url)
|
||||
const handleRouteChange = (url, { shallow }) => {
|
||||
console.log(
|
||||
`App is changing to ${url} ${
|
||||
shallow ? 'with' : 'without'
|
||||
} shallow routing`
|
||||
)
|
||||
}
|
||||
|
||||
router.events.on('routeChangeStart', handleRouteChange)
|
||||
|
|
|
@ -33,6 +33,10 @@ import resolveRewrites from './utils/resolve-rewrites'
|
|||
import { getRouteMatcher } from './utils/route-matcher'
|
||||
import { getRouteRegex } from './utils/route-regex'
|
||||
|
||||
interface RouteProperties {
|
||||
shallow: boolean
|
||||
}
|
||||
|
||||
interface TransitionOptions {
|
||||
shallow?: boolean
|
||||
locale?: string | false
|
||||
|
@ -652,8 +656,11 @@ export default class Router implements BaseRouter {
|
|||
performance.mark('routeChange')
|
||||
}
|
||||
|
||||
const { shallow = false } = options
|
||||
const routeProps = { shallow }
|
||||
|
||||
if (this._inFlightRoute) {
|
||||
this.abortComponentLoad(this._inFlightRoute)
|
||||
this.abortComponentLoad(this._inFlightRoute, routeProps)
|
||||
}
|
||||
|
||||
as = addBasePath(
|
||||
|
@ -677,12 +684,12 @@ export default class Router implements BaseRouter {
|
|||
// any time without notice.
|
||||
if (!(options as any)._h && this.onlyAHashChange(cleanedAs)) {
|
||||
this.asPath = cleanedAs
|
||||
Router.events.emit('hashChangeStart', as)
|
||||
Router.events.emit('hashChangeStart', as, routeProps)
|
||||
// TODO: do we need the resolved href when only a hash change?
|
||||
this.changeState(method, url, as, options)
|
||||
this.scrollToHash(cleanedAs)
|
||||
this.notify(this.components[this.route])
|
||||
Router.events.emit('hashChangeComplete', as)
|
||||
Router.events.emit('hashChangeComplete', as, routeProps)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -727,7 +734,6 @@ export default class Router implements BaseRouter {
|
|||
}
|
||||
|
||||
let route = removePathTrailingSlash(pathname)
|
||||
const { shallow = false } = options
|
||||
|
||||
// we need to resolve the as value using rewrites for dynamic SSG
|
||||
// pages to allow building the data URL correctly
|
||||
|
@ -827,7 +833,7 @@ export default class Router implements BaseRouter {
|
|||
}
|
||||
}
|
||||
|
||||
Router.events.emit('routeChangeStart', as)
|
||||
Router.events.emit('routeChangeStart', as, routeProps)
|
||||
|
||||
try {
|
||||
const routeInfo = await this.getRouteInfo(
|
||||
|
@ -835,7 +841,7 @@ export default class Router implements BaseRouter {
|
|||
pathname,
|
||||
query,
|
||||
as,
|
||||
shallow
|
||||
routeProps
|
||||
)
|
||||
let { error, props, __N_SSG, __N_SSP } = routeInfo
|
||||
|
||||
|
@ -869,7 +875,7 @@ export default class Router implements BaseRouter {
|
|||
return new Promise(() => {})
|
||||
}
|
||||
|
||||
Router.events.emit('beforeHistoryChange', as)
|
||||
Router.events.emit('beforeHistoryChange', as, routeProps)
|
||||
this.changeState(method, url, as, options)
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
|
@ -887,7 +893,7 @@ export default class Router implements BaseRouter {
|
|||
)
|
||||
|
||||
if (error) {
|
||||
Router.events.emit('routeChangeError', error, cleanedAs)
|
||||
Router.events.emit('routeChangeError', error, cleanedAs, routeProps)
|
||||
throw error
|
||||
}
|
||||
|
||||
|
@ -902,7 +908,7 @@ export default class Router implements BaseRouter {
|
|||
document.documentElement.lang = this.locale
|
||||
}
|
||||
}
|
||||
Router.events.emit('routeChangeComplete', as)
|
||||
Router.events.emit('routeChangeComplete', as, routeProps)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
|
@ -954,6 +960,7 @@ export default class Router implements BaseRouter {
|
|||
pathname: string,
|
||||
query: ParsedUrlQuery,
|
||||
as: string,
|
||||
routeProps: RouteProperties,
|
||||
loadErrorFail?: boolean
|
||||
): Promise<CompletePrivateRouteInfo> {
|
||||
if (err.cancelled) {
|
||||
|
@ -962,7 +969,7 @@ export default class Router implements BaseRouter {
|
|||
}
|
||||
|
||||
if (isAssetError(err) || loadErrorFail) {
|
||||
Router.events.emit('routeChangeError', err, as)
|
||||
Router.events.emit('routeChangeError', err, as, routeProps)
|
||||
|
||||
// If we can't load the page it could be one of following reasons
|
||||
// 1. Page doesn't exists
|
||||
|
@ -1034,7 +1041,14 @@ export default class Router implements BaseRouter {
|
|||
|
||||
return routeInfo
|
||||
} catch (routeInfoErr) {
|
||||
return this.handleRouteInfoError(routeInfoErr, pathname, query, as, true)
|
||||
return this.handleRouteInfoError(
|
||||
routeInfoErr,
|
||||
pathname,
|
||||
query,
|
||||
as,
|
||||
routeProps,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1043,13 +1057,13 @@ export default class Router implements BaseRouter {
|
|||
pathname: string,
|
||||
query: any,
|
||||
as: string,
|
||||
shallow: boolean = false
|
||||
routeProps: RouteProperties
|
||||
): Promise<PrivateRouteInfo> {
|
||||
try {
|
||||
const existingRouteInfo: PrivateRouteInfo | undefined = this.components[
|
||||
route
|
||||
]
|
||||
if (shallow && existingRouteInfo && this.route === route) {
|
||||
if (routeProps.shallow && existingRouteInfo && this.route === route) {
|
||||
return existingRouteInfo
|
||||
}
|
||||
|
||||
|
@ -1108,7 +1122,7 @@ export default class Router implements BaseRouter {
|
|||
this.components[route] = routeInfo
|
||||
return routeInfo
|
||||
} catch (err) {
|
||||
return this.handleRouteInfoError(err, pathname, query, as)
|
||||
return this.handleRouteInfoError(err, pathname, query, as, routeProps)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1351,9 +1365,14 @@ export default class Router implements BaseRouter {
|
|||
})
|
||||
}
|
||||
|
||||
abortComponentLoad(as: string): void {
|
||||
abortComponentLoad(as: string, routeProps: RouteProperties): void {
|
||||
if (this.clc) {
|
||||
Router.events.emit('routeChangeError', buildCancellationError(), as)
|
||||
Router.events.emit(
|
||||
'routeChangeError',
|
||||
buildCancellationError(),
|
||||
as,
|
||||
routeProps
|
||||
)
|
||||
this.clc()
|
||||
this.clc = null
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@ function useLoggedEvent(event, serializeArgs = (...args) => args) {
|
|||
}, [event, router.events, serializeArgs])
|
||||
}
|
||||
|
||||
function serializeErrorEventArgs(err, url) {
|
||||
return [err.message, err.cancelled, url]
|
||||
function serializeErrorEventArgs(err, url, properties) {
|
||||
return [err.message, err.cancelled, url, properties]
|
||||
}
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
|
|
|
@ -924,9 +924,9 @@ const runTests = (dev = false) => {
|
|||
|
||||
const eventLog = await browser.eval('window._getEventLog()')
|
||||
expect(eventLog).toEqual([
|
||||
['routeChangeStart', `${basePath}/other-page`],
|
||||
['beforeHistoryChange', `${basePath}/other-page`],
|
||||
['routeChangeComplete', `${basePath}/other-page`],
|
||||
['routeChangeStart', `${basePath}/other-page`, { shallow: false }],
|
||||
['beforeHistoryChange', `${basePath}/other-page`, { shallow: false }],
|
||||
['routeChangeComplete', `${basePath}/other-page`, { shallow: false }],
|
||||
])
|
||||
} finally {
|
||||
await browser.close()
|
||||
|
@ -941,8 +941,12 @@ const runTests = (dev = false) => {
|
|||
|
||||
const eventLog = await browser.eval('window._getEventLog()')
|
||||
expect(eventLog).toEqual([
|
||||
['hashChangeStart', `${basePath}/hello#some-hash`],
|
||||
['hashChangeComplete', `${basePath}/hello#some-hash`],
|
||||
['hashChangeStart', `${basePath}/hello#some-hash`, { shallow: false }],
|
||||
[
|
||||
'hashChangeComplete',
|
||||
`${basePath}/hello#some-hash`,
|
||||
{ shallow: false },
|
||||
],
|
||||
])
|
||||
} finally {
|
||||
await browser.close()
|
||||
|
@ -962,11 +966,17 @@ const runTests = (dev = false) => {
|
|||
|
||||
const eventLog = await browser.eval('window._getEventLog()')
|
||||
expect(eventLog).toEqual([
|
||||
['routeChangeStart', `${basePath}/slow-route`],
|
||||
['routeChangeError', 'Route Cancelled', true, `${basePath}/slow-route`],
|
||||
['routeChangeStart', `${basePath}/other-page`],
|
||||
['beforeHistoryChange', `${basePath}/other-page`],
|
||||
['routeChangeComplete', `${basePath}/other-page`],
|
||||
['routeChangeStart', `${basePath}/slow-route`, { shallow: false }],
|
||||
[
|
||||
'routeChangeError',
|
||||
'Route Cancelled',
|
||||
true,
|
||||
`${basePath}/slow-route`,
|
||||
{ shallow: false },
|
||||
],
|
||||
['routeChangeStart', `${basePath}/other-page`, { shallow: false }],
|
||||
['beforeHistoryChange', `${basePath}/other-page`, { shallow: false }],
|
||||
['routeChangeComplete', `${basePath}/other-page`, { shallow: false }],
|
||||
])
|
||||
} finally {
|
||||
await browser.close()
|
||||
|
@ -983,12 +993,13 @@ const runTests = (dev = false) => {
|
|||
|
||||
const eventLog = await browser.eval('window._getEventLog()')
|
||||
expect(eventLog).toEqual([
|
||||
['routeChangeStart', `${basePath}/error-route`],
|
||||
['routeChangeStart', `${basePath}/error-route`, { shallow: false }],
|
||||
[
|
||||
'routeChangeError',
|
||||
'Failed to load static props',
|
||||
null,
|
||||
`${basePath}/error-route`,
|
||||
{ shallow: false },
|
||||
],
|
||||
])
|
||||
} finally {
|
||||
|
|
Loading…
Reference in a new issue