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:
Leo Toneff 2020-12-17 10:14:50 +01:00 committed by GitHub
parent 0970873bb7
commit 25cb43a73b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 37 deletions

View file

@ -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)

View file

@ -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
}

View file

@ -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 }) {

View file

@ -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 {