Multiple event listeners in Next.js router
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 components can't listen to route state changes at the same time.
Based on the discussion on https://github.com/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?
javascript reactjs next.js
add a comment |
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 components can't listen to route state changes at the same time.
Based on the discussion on https://github.com/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?
javascript reactjs next.js
add a comment |
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 components can't listen to route state changes at the same time.
Based on the discussion on https://github.com/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?
javascript reactjs 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 components can't listen to route state changes at the same time.
Based on the discussion on https://github.com/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?
javascript reactjs next.js
javascript reactjs next.js
asked Oct 16 '17 at 12:29
Rafal PastuszakRafal Pastuszak
2,20222229
2,20222229
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
The solution I've been happy with so far involves wrapping next/router listener methods in Observables and creating a HLA attaching router events to components on componentDidMount.
An example implementation using RxJS:
// I'm using recompose 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 component subscribes to this observable
export const routeChangeComplete$ = Observable.create(
obs => {
Router.onRouteChangeComplete = () => {
console.log('route: complete')
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 component
import { lifecycle } from 'recompose'
import * as SharedRouter from './sharedRouter'
const withRouterEvents = lifecycle({
componentDidMount(){
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
]
},
componentWillUnmount(){
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>
add a comment |
Good news! The new canary Version of next.js 6.1.1-canary.2 will support multible router event listeners.
Install
$ npm i next@canary
Example
Router.events.on('routeChangeStart', (url) => {
console.log('App is changing to: ', url)
})
Docs
- Pull Request
- Documentation
How would you provide a listener attached to a specific route change, as in: After this specific route transition completes, do something?
– Avremel Kaminetzky
Jan 3 at 15:31
1
Router.events.on('routeChangeStart', (url) => { if (url === 'myURL') { // Do something } })
– HaNdTriX
Jan 3 at 15:40
How would that work for disabling the subscription? Say I have a global subscription for all route changes, and I want only turn off a single subscription.
– Avremel Kaminetzky
Jan 3 at 15:44
1
Oh don't use router events for those tasks! UsecomponentDidMount/componentWillUnmountfor subscriptions.
– HaNdTriX
Jan 3 at 15:51
1
gist.github.com/HaNdTriX/67dbcdafbffb950804225645de312870
– HaNdTriX
Jan 3 at 15:54
|
show 1 more comment
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f46770364%2fmultiple-event-listeners-in-next-js-router%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
The solution I've been happy with so far involves wrapping next/router listener methods in Observables and creating a HLA attaching router events to components on componentDidMount.
An example implementation using RxJS:
// I'm using recompose 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 component subscribes to this observable
export const routeChangeComplete$ = Observable.create(
obs => {
Router.onRouteChangeComplete = () => {
console.log('route: complete')
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 component
import { lifecycle } from 'recompose'
import * as SharedRouter from './sharedRouter'
const withRouterEvents = lifecycle({
componentDidMount(){
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
]
},
componentWillUnmount(){
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>
add a comment |
The solution I've been happy with so far involves wrapping next/router listener methods in Observables and creating a HLA attaching router events to components on componentDidMount.
An example implementation using RxJS:
// I'm using recompose 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 component subscribes to this observable
export const routeChangeComplete$ = Observable.create(
obs => {
Router.onRouteChangeComplete = () => {
console.log('route: complete')
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 component
import { lifecycle } from 'recompose'
import * as SharedRouter from './sharedRouter'
const withRouterEvents = lifecycle({
componentDidMount(){
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
]
},
componentWillUnmount(){
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>
add a comment |
The solution I've been happy with so far involves wrapping next/router listener methods in Observables and creating a HLA attaching router events to components on componentDidMount.
An example implementation using RxJS:
// I'm using recompose 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 component subscribes to this observable
export const routeChangeComplete$ = Observable.create(
obs => {
Router.onRouteChangeComplete = () => {
console.log('route: complete')
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 component
import { lifecycle } from 'recompose'
import * as SharedRouter from './sharedRouter'
const withRouterEvents = lifecycle({
componentDidMount(){
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
]
},
componentWillUnmount(){
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>
The solution I've been happy with so far involves wrapping next/router listener methods in Observables and creating a HLA attaching router events to components on componentDidMount.
An example implementation using RxJS:
// I'm using recompose 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 component subscribes to this observable
export const routeChangeComplete$ = Observable.create(
obs => {
Router.onRouteChangeComplete = () => {
console.log('route: complete')
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 component
import { lifecycle } from 'recompose'
import * as SharedRouter from './sharedRouter'
const withRouterEvents = lifecycle({
componentDidMount(){
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
]
},
componentWillUnmount(){
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>
answered Oct 16 '17 at 12:29
Rafal PastuszakRafal Pastuszak
2,20222229
2,20222229
add a comment |
add a comment |
Good news! The new canary Version of next.js 6.1.1-canary.2 will support multible router event listeners.
Install
$ npm i next@canary
Example
Router.events.on('routeChangeStart', (url) => {
console.log('App is changing to: ', url)
})
Docs
- Pull Request
- Documentation
How would you provide a listener attached to a specific route change, as in: After this specific route transition completes, do something?
– Avremel Kaminetzky
Jan 3 at 15:31
1
Router.events.on('routeChangeStart', (url) => { if (url === 'myURL') { // Do something } })
– HaNdTriX
Jan 3 at 15:40
How would that work for disabling the subscription? Say I have a global subscription for all route changes, and I want only turn off a single subscription.
– Avremel Kaminetzky
Jan 3 at 15:44
1
Oh don't use router events for those tasks! UsecomponentDidMount/componentWillUnmountfor subscriptions.
– HaNdTriX
Jan 3 at 15:51
1
gist.github.com/HaNdTriX/67dbcdafbffb950804225645de312870
– HaNdTriX
Jan 3 at 15:54
|
show 1 more comment
Good news! The new canary Version of next.js 6.1.1-canary.2 will support multible router event listeners.
Install
$ npm i next@canary
Example
Router.events.on('routeChangeStart', (url) => {
console.log('App is changing to: ', url)
})
Docs
- Pull Request
- Documentation
How would you provide a listener attached to a specific route change, as in: After this specific route transition completes, do something?
– Avremel Kaminetzky
Jan 3 at 15:31
1
Router.events.on('routeChangeStart', (url) => { if (url === 'myURL') { // Do something } })
– HaNdTriX
Jan 3 at 15:40
How would that work for disabling the subscription? Say I have a global subscription for all route changes, and I want only turn off a single subscription.
– Avremel Kaminetzky
Jan 3 at 15:44
1
Oh don't use router events for those tasks! UsecomponentDidMount/componentWillUnmountfor subscriptions.
– HaNdTriX
Jan 3 at 15:51
1
gist.github.com/HaNdTriX/67dbcdafbffb950804225645de312870
– HaNdTriX
Jan 3 at 15:54
|
show 1 more comment
Good news! The new canary Version of next.js 6.1.1-canary.2 will support multible router event listeners.
Install
$ npm i next@canary
Example
Router.events.on('routeChangeStart', (url) => {
console.log('App is changing to: ', url)
})
Docs
- Pull Request
- Documentation
Good news! The new canary Version of next.js 6.1.1-canary.2 will support multible router event listeners.
Install
$ npm i next@canary
Example
Router.events.on('routeChangeStart', (url) => {
console.log('App is changing to: ', url)
})
Docs
- Pull Request
- Documentation
edited Jan 3 at 15:40
answered Jul 22 '18 at 23:44
HaNdTriXHaNdTriX
19.1k65870
19.1k65870
How would you provide a listener attached to a specific route change, as in: After this specific route transition completes, do something?
– Avremel Kaminetzky
Jan 3 at 15:31
1
Router.events.on('routeChangeStart', (url) => { if (url === 'myURL') { // Do something } })
– HaNdTriX
Jan 3 at 15:40
How would that work for disabling the subscription? Say I have a global subscription for all route changes, and I want only turn off a single subscription.
– Avremel Kaminetzky
Jan 3 at 15:44
1
Oh don't use router events for those tasks! UsecomponentDidMount/componentWillUnmountfor subscriptions.
– HaNdTriX
Jan 3 at 15:51
1
gist.github.com/HaNdTriX/67dbcdafbffb950804225645de312870
– HaNdTriX
Jan 3 at 15:54
|
show 1 more comment
How would you provide a listener attached to a specific route change, as in: After this specific route transition completes, do something?
– Avremel Kaminetzky
Jan 3 at 15:31
1
Router.events.on('routeChangeStart', (url) => { if (url === 'myURL') { // Do something } })
– HaNdTriX
Jan 3 at 15:40
How would that work for disabling the subscription? Say I have a global subscription for all route changes, and I want only turn off a single subscription.
– Avremel Kaminetzky
Jan 3 at 15:44
1
Oh don't use router events for those tasks! UsecomponentDidMount/componentWillUnmountfor subscriptions.
– HaNdTriX
Jan 3 at 15:51
1
gist.github.com/HaNdTriX/67dbcdafbffb950804225645de312870
– HaNdTriX
Jan 3 at 15:54
How would you provide a listener attached to a specific route change, as in: After this specific route transition completes, do something?
– Avremel Kaminetzky
Jan 3 at 15:31
How would you provide a listener attached to a specific route change, as in: After this specific route transition completes, do something?
– Avremel Kaminetzky
Jan 3 at 15:31
1
1
Router.events.on('routeChangeStart', (url) => { if (url === 'myURL') { // Do something } })– HaNdTriX
Jan 3 at 15:40
Router.events.on('routeChangeStart', (url) => { if (url === 'myURL') { // Do something } })– HaNdTriX
Jan 3 at 15:40
How would that work for disabling the subscription? Say I have a global subscription for all route changes, and I want only turn off a single subscription.
– Avremel Kaminetzky
Jan 3 at 15:44
How would that work for disabling the subscription? Say I have a global subscription for all route changes, and I want only turn off a single subscription.
– Avremel Kaminetzky
Jan 3 at 15:44
1
1
Oh don't use router events for those tasks! Use
componentDidMount/componentWillUnmount for subscriptions.– HaNdTriX
Jan 3 at 15:51
Oh don't use router events for those tasks! Use
componentDidMount/componentWillUnmount for subscriptions.– HaNdTriX
Jan 3 at 15:51
1
1
gist.github.com/HaNdTriX/67dbcdafbffb950804225645de312870
– HaNdTriX
Jan 3 at 15:54
gist.github.com/HaNdTriX/67dbcdafbffb950804225645de312870
– HaNdTriX
Jan 3 at 15:54
|
show 1 more comment
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f46770364%2fmultiple-event-listeners-in-next-js-router%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
