diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx
index a951d2979b5..13815fd3efc 100644
--- a/packages/remix-react/components.tsx
+++ b/packages/remix-react/components.tsx
@@ -864,29 +864,6 @@ function dedupe(array: any[]) {
return [...new Set(array)];
}
-/**
- * Setup a callback to be fired on the window's `beforeunload` event. This is
- * useful for saving some data to `window.localStorage` just before the page
- * refreshes, which automatically happens on the next `` click when Remix
- * detects a new version of the app is available on the server.
- *
- * Note: The `callback` argument should be a function created with
- * `React.useCallback()`.
- *
- * @see https://remix.run/api/remix#usebeforeunload
- */
-export function useBeforeUnload(
- callback: (event: BeforeUnloadEvent) => any
-): void {
- // TODO: Export from react-router-dom
- React.useEffect(() => {
- window.addEventListener("beforeunload", callback);
- return () => {
- window.removeEventListener("beforeunload", callback);
- };
- }, [callback]);
-}
-
// TODO: Can this be re-exported from RR?
export interface RouteMatch {
/**
diff --git a/packages/remix-react/index.tsx b/packages/remix-react/index.tsx
index 19e3d04a65b..a571864c16f 100644
--- a/packages/remix-react/index.tsx
+++ b/packages/remix-react/index.tsx
@@ -12,6 +12,7 @@ export type {
export {
Form,
Outlet,
+ useBeforeUnload,
useFormAction,
useHref,
useLocation,
@@ -48,7 +49,6 @@ export {
useLoaderData,
useMatches,
useActionData,
- useBeforeUnload,
} from "./components";
export type { FormMethod, FormEncType } from "./data";
diff --git a/packages/remix-react/scroll-restoration.tsx b/packages/remix-react/scroll-restoration.tsx
index 59ce0dd5c91..b2033ece1c5 100644
--- a/packages/remix-react/scroll-restoration.tsx
+++ b/packages/remix-react/scroll-restoration.tsx
@@ -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,
+} 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