Currently next/router
exposes a singleton API where listening to its changes can be done via:
Router.onRouteChangeStart = myHandler // subscribe
Router.onRouteChangeStart = null // unsubscribe
This poses several architecture-related challenges as two unrelated ponents can't listen to route state changes at the same time.
Based on the discussion on .js/issues/2033 there is no plan to convert next/router
to an Event Emitter / Observable.
Given that, how can we implement a router with shared subscriptions in Next.js?
Currently next/router
exposes a singleton API where listening to its changes can be done via:
Router.onRouteChangeStart = myHandler // subscribe
Router.onRouteChangeStart = null // unsubscribe
This poses several architecture-related challenges as two unrelated ponents can't listen to route state changes at the same time.
Based on the discussion on https://github./zeit/next.js/issues/2033 there is no plan to convert next/router
to an Event Emitter / Observable.
Given that, how can we implement a router with shared subscriptions in Next.js?
Share Improve this question asked Oct 16, 2017 at 12:29 Rafal PastuszakRafal Pastuszak 3,1602 gold badges31 silver badges31 bronze badges2 Answers
Reset to default 3The solution I've been happy with so far involves wrapping next/router
listener methods in Observables and creating a HLA attaching router events to ponents on ponentDidMount.
An example implementation using RxJS:
// I'm using repose for and rxjs, but you should be able to modify this code easily
// 1. sharedRouter.js
import Router from 'next/router'
import { Observable } from 'rxjs'
export const routeChangeStart$ = Observable.create(
obs => {
console.log('route: start')
Router.onRouteChangeStart = url => {
obs.next(url)
}
}
).share() // note the .share() operator,
// it ensures that we don't reassign Router.onRouteChangeStart
// every time a new ponent subscribes to this observable
export const routeChangeComplete$ = Observable.create(
obs => {
Router.onRouteChangeComplete = () => {
console.log('route: plete')
obs.next()
}
}
).share()
export const routeChangeError$ = Observable.create(
obs => {
Router.onRouteChangeError = () => {
console.log('route: error')
obs.next()
}
}
).share()
// 2. appBar/withRouterEvents.js
// This one is attached to our AppNav ponent
import { lifecycle } from 'repose'
import * as SharedRouter from './sharedRouter'
const withRouterEvents = lifecycle({
ponentDidMount(){
const onStartLoadingSub = Router.routeChangeStart$
.subscribe(
() => {
// hide nav
// show loading indicator
}
)
const onFinishLoadingSub = Router
.routeChangeError$
.merge(Router.routeChangeComplete$)
.subscribe(
() => {
// hide loading indicator
}
)
this.subs = [
onStartLoadingSub,
onFinishLoadingSub
]
},
ponentWillUnmount(){
if(!Array.isArray(this.subs)) return;
this.subs.forEach(
sub => sub.unsubscribe()
)
}
})
// 3. appBar/index.js
export default ({
isNavVisible,
isLoading,
children
}) => <nav className={
isNavVisible ? 'app-bar' : 'app-bar app-bar--hidden'
}>
<LoadingIndicator isActive={isLoading} />
{children}
</nav>
Good news! As from Version of Next.js 6.1.1
multiple router event listeners will be allowed:
Example
Router.events.on('routeChangeStart', (url) => {
console.log('App is changing to: ', url)
})
Docs
- Pull Request
- Documentation