-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ScrollRestoration on RR 6.4 #4844
Merged
brophdawg11
merged 4 commits into
feat/rrr-rendering
from
brophdawg11/scroll-restoration-6-4
Dec 16, 2022
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
5659a46
ScrollRestoration on RR 6.4
brophdawg11 47ca3bb
Re-export useBeforeUnload from react-router-dom
brophdawg11 5f09fb3
Remove uneeded skip param
brophdawg11 2264efa
Merge branch 'feat/rrr-rendering' into brophdawg11/scroll-restoration…
brophdawg11 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,60 @@ | ||
import * as React from "react"; | ||
import { useLocation } from "react-router-dom"; | ||
import type { ScrollRestorationProps as ScrollRestorationPropsRR } from "react-router-dom"; | ||
import { | ||
useLocation, | ||
UNSAFE_useScrollRestoration as useScrollRestoration, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The hook was private in both Remix and RR so kept it that way via the |
||
} from "react-router-dom"; | ||
|
||
import { useBeforeUnload, useTransition } from "./components"; | ||
import type { ScriptProps } from "./components"; | ||
import { useMatches } 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(props: ScriptProps) { | ||
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"; | ||
}, []) | ||
export function ScrollRestoration({ | ||
getKey, | ||
...props | ||
}: ScriptProps & { | ||
getKey: ScrollRestorationPropsRR["getKey"]; | ||
}) { | ||
let location = useLocation(); | ||
let matches = useMatches(); | ||
|
||
useScrollRestoration({ | ||
getKey, | ||
storageKey: STORAGE_KEY, | ||
}); | ||
|
||
// In order to support `getKey`, we need to compute a "key" here so we can | ||
// hydrate that up so that SSR scroll restoration isn't waiting on React to | ||
// hydrate. *However*, our key on the server is not the same as our key on | ||
// the client! So if the user's getKey implementation returns the SSR | ||
// location key, then let's ignore it and let our inline <script> below pick | ||
// up the client side history state key | ||
let key = React.useMemo( | ||
() => { | ||
if (!getKey) return null; | ||
let userKey = getKey(location, matches); | ||
return userKey !== location.key ? userKey : null; | ||
}, | ||
// Nah, we only need this the first time for the SSR render | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[] | ||
); | ||
|
||
let restoreScroll = ((STORAGE_KEY: string) => { | ||
let restoreScroll = ((STORAGE_KEY: string, restoreKey: 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]; | ||
let storedY = positions[restoreKey || window.history.state.key]; | ||
if (typeof storedY === "number") { | ||
window.scrollTo(0, storedY); | ||
} | ||
|
@@ -58,79 +69,10 @@ export function ScrollRestoration(props: ScriptProps) { | |
{...props} | ||
suppressHydrationWarning | ||
dangerouslySetInnerHTML={{ | ||
__html: `(${restoreScroll})(${JSON.stringify(STORAGE_KEY)})`, | ||
__html: `(${restoreScroll})(${JSON.stringify( | ||
STORAGE_KEY | ||
)}, ${JSON.stringify(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]); | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅