Skip to content

Commit f8194fd

Browse files
authored
Handle case when session storage is blocked (#10848)
1 parent e7a8f10 commit f8194fd

File tree

4 files changed

+165
-4
lines changed

4 files changed

+165
-4
lines changed

.changeset/soft-forks-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router-dom": patch
3+
---
4+
5+
Log a warning and fail gracefully in `ScrollRestoration` when `sessionStorage` is unavailable

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
- danielberndt
5353
- daniilguit
5454
- dauletbaev
55+
- david-bezero
5556
- david-crespo
5657
- decadentsavant
5758
- DigitalNaut

packages/react-router-dom/__tests__/scroll-restoration-test.tsx

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,63 @@ import {
1313
import getHtml from "../../react-router/__tests__/utils/getHtml";
1414

1515
describe(`ScrollRestoration`, () => {
16+
it("restores the scroll position for a page when re-visited", () => {
17+
const consoleWarnMock = jest
18+
.spyOn(console, "warn")
19+
.mockImplementation(() => {});
20+
21+
let testWindow = getWindowImpl("/base");
22+
const mockScroll = jest.fn();
23+
window.scrollTo = mockScroll;
24+
25+
let router = createBrowserRouter(
26+
[
27+
{
28+
path: "/",
29+
Component() {
30+
return (
31+
<>
32+
<Outlet />
33+
<ScrollRestoration
34+
getKey={(location) => "test1-" + location.pathname}
35+
/>
36+
</>
37+
);
38+
},
39+
children: testPages,
40+
},
41+
],
42+
{ basename: "/base", window: testWindow }
43+
);
44+
let { container } = render(<RouterProvider router={router} />);
45+
46+
expect(getHtml(container)).toMatch("On page 1");
47+
48+
// simulate scrolling
49+
Object.defineProperty(window, "scrollY", { writable: true, value: 100 });
50+
51+
// leave page
52+
window.dispatchEvent(new Event("pagehide"));
53+
fireEvent.click(screen.getByText("Go to page 2"));
54+
expect(getHtml(container)).toMatch("On page 2");
55+
56+
// return to page
57+
window.dispatchEvent(new Event("pagehide"));
58+
fireEvent.click(screen.getByText("Go to page 1"));
59+
60+
expect(getHtml(container)).toMatch("On page 1");
61+
62+
// check scroll activity
63+
expect(mockScroll.mock.calls).toEqual([
64+
[0, 0],
65+
[0, 0],
66+
[0, 100], // restored
67+
]);
68+
69+
expect(consoleWarnMock).not.toHaveBeenCalled();
70+
consoleWarnMock.mockRestore();
71+
});
72+
1673
it("removes the basename from the location provided to getKey", () => {
1774
let getKey = jest.fn(() => "mykey");
1875
let testWindow = getWindowImpl("/base");
@@ -64,8 +121,99 @@ describe(`ScrollRestoration`, () => {
64121
// @ts-expect-error
65122
expect(getKey.mock.calls[2][0].pathname).toBe("/page"); // restore
66123
});
124+
125+
it("fails gracefully if sessionStorage is not available", () => {
126+
const consoleWarnMock = jest
127+
.spyOn(console, "warn")
128+
.mockImplementation(() => {});
129+
130+
let testWindow = getWindowImpl("/base");
131+
const mockScroll = jest.fn();
132+
window.scrollTo = mockScroll;
133+
134+
jest.spyOn(window, "sessionStorage", "get").mockImplementation(() => {
135+
throw new Error("denied");
136+
});
137+
138+
let router = createBrowserRouter(
139+
[
140+
{
141+
path: "/",
142+
Component() {
143+
return (
144+
<>
145+
<Outlet />
146+
<ScrollRestoration
147+
getKey={(location) => "test2-" + location.pathname}
148+
/>
149+
</>
150+
);
151+
},
152+
children: testPages,
153+
},
154+
],
155+
{ basename: "/base", window: testWindow }
156+
);
157+
let { container } = render(<RouterProvider router={router} />);
158+
159+
expect(getHtml(container)).toMatch("On page 1");
160+
161+
// simulate scrolling
162+
Object.defineProperty(window, "scrollY", { writable: true, value: 100 });
163+
164+
// leave page
165+
window.dispatchEvent(new Event("pagehide"));
166+
fireEvent.click(screen.getByText("Go to page 2"));
167+
expect(getHtml(container)).toMatch("On page 2");
168+
169+
// return to page
170+
window.dispatchEvent(new Event("pagehide"));
171+
fireEvent.click(screen.getByText("Go to page 1"));
172+
173+
expect(getHtml(container)).toMatch("On page 1");
174+
175+
// check scroll activity
176+
expect(mockScroll.mock.calls).toEqual([
177+
[0, 0],
178+
[0, 0],
179+
[0, 100], // restored (still possible because the user hasn't left the page)
180+
]);
181+
182+
expect(consoleWarnMock).toHaveBeenCalledWith(
183+
expect.stringContaining(
184+
"Failed to save scroll positions in sessionStorage"
185+
)
186+
);
187+
188+
consoleWarnMock.mockRestore();
189+
});
67190
});
68191

192+
const testPages = [
193+
{
194+
index: true,
195+
Component() {
196+
return (
197+
<p>
198+
On page 1<br />
199+
<Link to="/page">Go to page 2</Link>
200+
</p>
201+
);
202+
},
203+
},
204+
{
205+
path: "page",
206+
Component() {
207+
return (
208+
<p>
209+
On page 2<br />
210+
<Link to="/">Go to page 1</Link>
211+
</p>
212+
);
213+
},
214+
},
215+
];
216+
69217
function getWindowImpl(initialUrl: string): Window {
70218
// Need to use our own custom DOM in order to get a working history
71219
const dom = new JSDOM(`<!DOCTYPE html>`, { url: "http://localhost/" });

packages/react-router-dom/index.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,10 +1323,17 @@ function useScrollRestoration({
13231323
let key = (getKey ? getKey(location, matches) : null) || location.key;
13241324
savedScrollPositions[key] = window.scrollY;
13251325
}
1326-
sessionStorage.setItem(
1327-
storageKey || SCROLL_RESTORATION_STORAGE_KEY,
1328-
JSON.stringify(savedScrollPositions)
1329-
);
1326+
try {
1327+
sessionStorage.setItem(
1328+
storageKey || SCROLL_RESTORATION_STORAGE_KEY,
1329+
JSON.stringify(savedScrollPositions)
1330+
);
1331+
} catch (error) {
1332+
warning(
1333+
false,
1334+
`Failed to save scroll positions in sessionStorage, <ScrollRestoration /> will not work properly (${error}).`
1335+
);
1336+
}
13301337
window.history.scrollRestoration = "auto";
13311338
}, [storageKey, getKey, navigation.state, location, matches])
13321339
);

0 commit comments

Comments
 (0)