-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
scroll-restoration.tsx
135 lines (113 loc) · 3.29 KB
/
scroll-restoration.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import * as React from "react";
import { useLocation } from "react-router-dom";
import { useBeforeUnload, useTransition } from "./components";
let STORAGE_KEY = "positions";
let positions: { [key: string]: number } = {};
if (typeof document !== "undefined") {
let sessionPositions = sessionStorage.getItem(STORAGE_KEY);
if (sessionPositions) {
positions = JSON.parse(sessionPositions);
}
}
/**
* This component will emulate the browser's scroll restoration on location
* changes.
*
* @see https://remix.run/api/remix#scrollrestoration
*/
export function ScrollRestoration({ nonce = undefined }: { nonce?: string }) {
useScrollRestoration();
// wait for the browser to restore it on its own
React.useEffect(() => {
window.history.scrollRestoration = "manual";
}, []);
// let the browser restore on it's own for refresh
useBeforeUnload(
React.useCallback(() => {
window.history.scrollRestoration = "auto";
}, [])
);
let restoreScroll = ((STORAGE_KEY: string) => {
if (!window.history.state || !window.history.state.key) {
let key = Math.random().toString(32).slice(2);
window.history.replaceState({ key }, "");
}
try {
let positions = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "{}");
let storedY = positions[window.history.state.key];
if (typeof storedY === "number") {
window.scrollTo(0, storedY);
}
} catch (error) {
console.error(error);
sessionStorage.removeItem(STORAGE_KEY);
}
}).toString();
return (
<script
nonce={nonce}
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `(${restoreScroll})(${JSON.stringify(STORAGE_KEY)})`,
}}
/>
);
}
let hydrated = false;
function useScrollRestoration() {
let location = useLocation();
let transition = useTransition();
let wasSubmissionRef = React.useRef(false);
React.useEffect(() => {
if (transition.submission) {
wasSubmissionRef.current = true;
}
}, [transition]);
React.useEffect(() => {
if (transition.location) {
positions[location.key] = window.scrollY;
}
}, [transition, location]);
useBeforeUnload(
React.useCallback(() => {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(positions));
}, [])
);
if (typeof document !== "undefined") {
// eslint-disable-next-line
React.useLayoutEffect(() => {
// don't do anything on hydration, the component already did this with an
// inline script.
if (!hydrated) {
hydrated = true;
return;
}
let y = positions[location.key];
// been here before, scroll to it
if (y != undefined) {
window.scrollTo(0, y);
return;
}
// try to scroll to the hash
if (location.hash) {
let el = document.getElementById(location.hash.slice(1));
if (el) {
el.scrollIntoView();
return;
}
}
// don't do anything on submissions
if (wasSubmissionRef.current === true) {
wasSubmissionRef.current = false;
return;
}
// otherwise go to the top on new locations
window.scrollTo(0, 0);
}, [location]);
}
React.useEffect(() => {
if (transition.submission) {
wasSubmissionRef.current = true;
}
}, [transition]);
}