Right now, it only seems to be possible to attach one event listener to the router for each event, since this is defined as a single property on the Router singleton instance:
// Add listener
Router.onRouterEvent = myCallback;
// Remove
Router.onRouterEvent = null
I think it would be nice to be able to add an arbitrary number of event listeners in any part of the application. Suggested API:
// Add event listener
Router.on('routerEvent', callback);
// Remove event listeners
Router.off('routerEvent', callback);
Or is there already a way to do that that I missed?
@ivome Currently you've to do it yourself. We didn't do it because there's no standard event listener API.
@arunoda Would you be open to a pull request?
I think we didn't wanted to do that. I hope @rauchg can answer this more.
It can also be implemented as an add on or external package, but I think the uses cases where you need a feature like that are quite common:
Any news on this issue? Anybody has made a custom implementation?
@acanimal not sure if that helps but I've posted a question with an example implementation on SO: https://stackoverflow.com/questions/46770364/multiple-event-listeners-in-next-js-router/46770365#46770365
I'm using rxjs and recompose, but you could easily replace them with a singleton event emitter and regular component lifecycle hooks.
Edit: hopefully this won't sound like I'm preaching (it's just a personal preference), but a nice thing about using Observables in this scenario is that it makes it trivial to track/store the entire user navigation flow - I'm using it to catch referrers when navigating between pages (and different versions of the same page).
Edit2: Added gist
@arunoda @rauchg This really needs to be reopened. This API limitation is not obvious to a consumer and neither is a workaround. We are not able to publically publish components that rely on router events when consumers are already using the events themselves or have implemented custom workarounds.
I have a global loading indicator component that taps into onRouteChangeComplete
. I also have to use the same listener to record gtag.js page load events.
Whichever code runs last works, overwriting the functionality of the other. I guess I'll have to come up with some sort of global singleton module setup.
https://github.com/zeit/next.js/blob/4.1.0/lib/EventEmitter.js could be replaced with something like https://github.com/scottcorgan/tiny-emitter.
Got a workaround working…
Install tiny-emitter
:
npm install tiny-emitter
Create router-events.js
:
import Emitter from 'tiny-emitter'
import Router from 'next/router'
const emitter = new Emitter()
const methodEvents = {
onRouteChangeStart: 'routeChangeStart',
onRouteChangeComplete: 'routeChangeComplete',
onRouteChangeError: 'routeChangeError',
onBeforeHistoryChange: 'beforeHistoryChange',
onAppUpdated: 'appUpdated'
}
Object.keys(methodEvents).forEach(method => {
Router[method] = (...args) => emitter.emit(methodEvents[method], ...args)
})
export default emitter
In components/foo.js
:
import routerEvents from '../router-events.js'
const handleRouteChangeComplete1 = url => console.log(url)
const handleRouteChangeComplete2 = url => console.log(url)
routerEvents.on('routeChangeComplete', handleChangeComplete1)
routerEvents.on('routeChangeComplete', handleChangeComplete2)
routerEvents.off('routeChangeComplete', handleChangeComplete1)
From this point on be sure to always use routerEvents
; if you use the router API directly anywhere it will break the setup.
I published next-router-events
to make this easier for everyone.
Thanks @jaydenseric!
This thread has been automatically locked because it has not had recent activity. Please open a new issue for related bugs and link to relevant comments in this thread.
Most helpful comment
I published
next-router-events
to make this easier for everyone.