Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple event listeners per event on Router #2033

Closed
ivome opened this issue May 21, 2017 · 12 comments
Closed

Multiple event listeners per event on Router #2033

ivome opened this issue May 21, 2017 · 12 comments

Comments

@ivome
Copy link

ivome commented May 21, 2017

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?

@arunoda
Copy link
Contributor

arunoda commented May 21, 2017

@ivome Currently you've to do it yourself. We didn't do it because there's no standard event listener API.

@arunoda arunoda closed this as completed May 21, 2017
@ivome
Copy link
Author

ivome commented May 21, 2017

@arunoda Would you be open to a pull request?

@arunoda
Copy link
Contributor

arunoda commented May 21, 2017

I think we didn't wanted to do that. I hope @rauchg can answer this more.

@ivome
Copy link
Author

ivome commented May 21, 2017

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:

  • Loading indicator in react components + any logic that needs to listen to router events (data fetching, authentication, redirects etc.)
  • Loading indicator in multiple components

@acanimal
Copy link

acanimal commented Sep 5, 2017

Any news on this issue? Anybody has made a custom implementation?

@paprikka
Copy link

paprikka commented Oct 16, 2017

@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

sharedRouter.js (gist)

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

@jaydenseric
Copy link
Contributor

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

@jaydenseric
Copy link
Contributor

@jaydenseric
Copy link
Contributor

jaydenseric commented Oct 17, 2017

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.

@jaydenseric
Copy link
Contributor

I published next-router-events to make this easier for everyone.

@timneutkens
Copy link
Member

Thanks @jaydenseric!

@lock
Copy link

lock bot commented May 10, 2018

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.

@lock lock bot locked as resolved and limited conversation to collaborators May 10, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants