Skip to content

Commit

Permalink
Add Router method to execute custom logic before popstate events (#3956)
Browse files Browse the repository at this point in the history
* Add router method to inject code before popstate events

* Default _beforePopState, return true

* Fix link in README

* Re-order `if` statements per feedback
  • Loading branch information
gcpantazis authored and timneutkens committed Mar 31, 2018
1 parent a785f30 commit 085b2f8
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const SingletonRouter = {

// Create public properties and methods of the router in the SingletonRouter
const propertyFields = ['components', 'pathname', 'route', 'query', 'asPath']
const coreMethodFields = ['push', 'replace', 'reload', 'back', 'prefetch']
const routerEvents = ['routeChangeStart', 'beforeHistoryChange', 'routeChangeComplete', 'routeChangeError']
const coreMethodFields = ['push', 'replace', 'reload', 'back', 'prefetch', 'beforePopState']

propertyFields.forEach((field) => {
// Here we need to use Object.defineProperty because, we need to return
Expand Down
11 changes: 11 additions & 0 deletions lib/router/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class Router {
this.subscriptions = new Set()
this.componentLoadCancel = null
this.onPopState = this.onPopState.bind(this)
this._beforePopState = () => true

if (typeof window !== 'undefined') {
// in order for `e.state` to work on the `onpopstate` event
Expand All @@ -66,6 +67,12 @@ export default class Router {
return
}

// If the downstream application returns falsy, return.
// They will then be responsible for handling the event.
if (!this._beforePopState(e.state)) {
return
}

const { url, as, options } = e.state
this.replace(url, as, options)
}
Expand Down Expand Up @@ -265,6 +272,10 @@ export default class Router {
this.notify(data)
}

beforePopState (cb) {
this._beforePopState = cb
}

onlyAHashChange (as) {
if (!this.asPath) return false
const [ oldUrlNoHash, oldHash ] = this.asPath.split('#')
Expand Down
33 changes: 33 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,31 @@ export default () =>
</div>
```

#### Intercepting `popstate`

In some cases (for example, if using a [custom router](#custom-server-and-routing)), you may wish
to listen to `popstate` and react before the router acts on it.
For example, you could use this to manipulate the request, or force an SSR refresh.

```jsx
import Router from 'next/router'

Router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as !== "/" || as !== "/other") {
// Have SSR render bad routes as a 404.
window.location.href = as
return false
}

return true
});
```

If you return a falsy value from `beforePopState`, `Router` will not handle `popstate`;
you'll be responsible for handling it, in that case.
See [Disabling File-System Routing](#disabling-file-system-routing).

Above `Router` object comes with the following API:

- `route` - `String` of the current route
Expand All @@ -466,6 +491,7 @@ Above `Router` object comes with the following API:
- `asPath` - `String` of the actual path (including the query) shows in the browser
- `push(url, as=url)` - performs a `pushState` call with the given url
- `replace(url, as=url)` - performs a `replaceState` call with the given url
- `beforePopState(cb=function)` - intercept popstate before router processes the event.

The second `as` parameter for `push` and `replace` is an optional _decoration_ of the URL. Useful if you configured custom routes on the server.

Expand Down Expand Up @@ -753,6 +779,13 @@ module.exports = {
}
```

Note that `useFileSystemPublicRoutes` simply disables filename routes from SSR; client-side routing
may still access those paths. If using this option, you should guard against navigation to routes
you do not want programmatically.

You may also wish to configure the client-side Router to disallow client-side redirects to filename
routes; please refer to [Intercepting `popstate`](#intercepting-popstate).

#### Dynamic assetPrefix

Sometimes we need to set the `assetPrefix` dynamically. This is useful when changing the `assetPrefix` based on incoming requests.
Expand Down

0 comments on commit 085b2f8

Please sign in to comment.