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