Skip to content

Commit aa684c9

Browse files
committed
Fix back button render issue
Resolves an issue where pressing back button after clicking on a new route caused previous route not to be cleared from DOM.
1 parent 6ef7670 commit aa684c9

File tree

2 files changed

+67
-44
lines changed

2 files changed

+67
-44
lines changed

src/router-component.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@ export class RouterComponent extends HTMLElement {
7373
if (!triggerRouteChange) {
7474
delete state.triggerRouteChange;
7575
}
76-
this.previousLocation = { ...this.location };
7776
method.call(history, state, title, url);
78-
79-
if (triggerRouteChange) {
80-
this.showRoute(url);
77+
if (!triggerRouteChange) {
78+
return;
79+
}
80+
if (this.previousLocation) {
81+
this.hideRoute(this.previousLocation.pathname);
8182
}
83+
this.showRoute(url);
8284
};
8385
}
8486
this.showRoute(this.getFullPathname(this.location));
@@ -154,7 +156,7 @@ export class RouterComponent extends HTMLElement {
154156
if (!location) return;
155157
const [pathname, hashString] = location.split('#');
156158
const routeElement = this.getRouteElementByPath(pathname);
157-
159+
this.previousLocation = { ...this.location };
158160
if (!routeElement) {
159161
return console.warn(
160162
`Navigated to path "${pathname}" but there is no matching element with a path ` +
@@ -316,10 +318,9 @@ export class RouterComponent extends HTMLElement {
316318

317319
private async popStateChanged() {
318320
const path = this.getFullPathname(this.location);
319-
if (this.location.href !== this.previousLocation.href) {
320-
this.hideRoute(this.previousLocation.pathname);
321-
}
322-
this.showRoute(path);
321+
// although popstate was called we still need to trigger
322+
// replaceState so all stateful operations can be performed
323+
window.history.replaceState({}, document.title, path);
323324
}
324325

325326
private setupElement(routeElement: Element) {

tests/router-component-tests.ts

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import sinon from 'sinon';
2-
import { expect, fixture, html } from '@open-wc/testing';
2+
import { expect, fixture, html, nextFrame } from '@open-wc/testing';
33
// eslint-disable-next-line no-unused-vars
44
import { extractPathParams, RouterComponent } from '../src/router-component';
55

@@ -98,40 +98,6 @@ describe('<router-component>', async () => {
9898
expect(document.body.querySelector('second-page')).to.be.null;
9999
});
100100

101-
it('switches to the child that has the path that matches the current location after popstate has been called', async () => {
102-
await fixture(html`
103-
<router-component>
104-
<first-page path="/page1"></first-page>
105-
<second-page path="/page2"></second-page>
106-
</router-component>
107-
`);
108-
109-
window.history.pushState({}, document.title, '/page1');
110-
window.history.pushState({}, document.title, '/page2');
111-
const popstate = new PopStateEvent('popstate', { state: {} });
112-
window.dispatchEvent(popstate);
113-
expect(document.body.querySelector('first-page')).to.be.null;
114-
expect(document.body.querySelector('second-page')).to.not.be.null;
115-
});
116-
117-
it('shows a warning when attempting to go to a route that is not handled after popstate is called', async () => {
118-
const component: RouterComponent = await fixture(html`
119-
<router-component>
120-
<first-page path="/page1"></first-page>
121-
<second-page path="/page2"></second-page>
122-
</router-component>
123-
`);
124-
125-
window.history.pushState({}, document.title, '/page1');
126-
const newPath = 'nope';
127-
consoleWarn.resetHistory();
128-
component.show(newPath);
129-
expect(consoleWarn.args[0]).to.deep.equal([
130-
`Navigated to path "${newPath}" but there is no matching ` +
131-
`element with a path that matches. Maybe you should implement a catch-all route with the path attribute of ".*"?`,
132-
]);
133-
});
134-
135101
it('shows the child whose path matches the catch all url', async () => {
136102
await fixture(html`
137103
<router-component>
@@ -382,6 +348,62 @@ describe('<router-component>', async () => {
382348
expect(disconnectedCallbackSpy.callCount).to.equal(1);
383349
});
384350

351+
describe('when popstate is triggered', () => {
352+
it('switches to the child that has the path that matches the current location', async () => {
353+
await fixture(html`
354+
<router-component>
355+
<first-page path="/page1"></first-page>
356+
<second-page path="/page2"></second-page>
357+
</router-component>
358+
`);
359+
360+
window.history.pushState({}, document.title, '/page1');
361+
window.history.pushState({}, document.title, '/page2');
362+
const popstate = new PopStateEvent('popstate', { state: {} });
363+
window.dispatchEvent(popstate);
364+
expect(document.body.querySelector('first-page')).to.be.null;
365+
expect(document.body.querySelector('second-page')).to.not.be.null;
366+
});
367+
368+
it('shows a warning when attempting to go to a route that is not handled', async () => {
369+
const component: RouterComponent = await fixture(html`
370+
<router-component>
371+
<first-page path="/page1"></first-page>
372+
<second-page path="/page2"></second-page>
373+
</router-component>
374+
`);
375+
376+
window.history.pushState({}, document.title, '/page1');
377+
const newPath = 'nope';
378+
consoleWarn.resetHistory();
379+
component.show(newPath);
380+
expect(consoleWarn.args[0]).to.deep.equal([
381+
`Navigated to path "${newPath}" but there is no matching ` +
382+
`element with a path that matches. Maybe you should implement a catch-all route with the path attribute of ".*"?`,
383+
]);
384+
});
385+
386+
it('removes previous route element after clicking back button', async () => {
387+
await fixture(html`
388+
<router-component>
389+
<first-page path="/page1">
390+
<a href="/page2">To page 2</a>
391+
</first-page>
392+
<second-page path="/page2"></second-page>
393+
</router-component>
394+
`);
395+
396+
window.history.pushState({}, document.title, '/page1');
397+
const firstPageLink: HTMLAnchorElement =
398+
document.querySelector('first-page a');
399+
firstPageLink.click(); // go to second
400+
window.history.back();
401+
await nextFrame();
402+
expect(document.body.querySelector('first-page')).to.not.be.null;
403+
expect(document.body.querySelector('second-page')).to.be.null;
404+
});
405+
});
406+
385407
describe('when pushState or replaceState is overridden', () => {
386408
const origPushState = window.history.pushState;
387409
const origReplaceState = window.history.replaceState;

0 commit comments

Comments
 (0)