Skip to content

Commit

Permalink
fix: pass useMatches objects to ScrollRestoration getKey (#9157)
Browse files Browse the repository at this point in the history
* docs: minor typing updates to align with docs

* fix up types for scroll restoration matches

* add changeset

* add comments about DRY
  • Loading branch information
brophdawg11 committed Aug 18, 2022
1 parent 70ad685 commit 5c8fdec
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 20 deletions.
6 changes: 6 additions & 0 deletions .changeset/beige-buckets-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"react-router-dom": patch
"@remix-run/router": patch
---

fix: pass useMatches objects to ScrollRestoration getKey (#9157)
8 changes: 4 additions & 4 deletions examples/scroll-restoration/src/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from "react";

import {
type DataRouteMatch,
type Location,
Link,
Outlet,
ScrollRestoration,
useLoaderData,
useLocation,
useNavigation,
useMatches,
} from "react-router-dom";

export function Layout() {
Expand All @@ -22,9 +22,9 @@ export function Layout() {
// previously-accessed path. Or - go nuts and lump many pages into a
// single key (i.e., anything /wizard/* uses the same key)!
let getKey = React.useCallback(
(location: Location, matches: DataRouteMatch[]) => {
let match = matches.find((m) => m.route.handle?.scrollMode);
if (match?.route.handle?.scrollMode === "pathname") {
(location: Location, matches: ReturnType<typeof useMatches>) => {
let match = matches.find((m) => (m.handle as any)?.scrollMode);
if ((match?.handle as any)?.scrollMode === "pathname") {
return location.pathname;
}

Expand Down
18 changes: 7 additions & 11 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
NavigateOptions,
RouteObject,
To,
useMatches,
useNavigation,
} from "react-router";
import {
Router,
Expand Down Expand Up @@ -1017,6 +1019,8 @@ function useScrollRestoration({
storageKey?: string;
} = {}) {
let location = useLocation();
let matches = useMatches();
let navigation = useNavigation();
let dataRouterContext = React.useContext(DataRouterContext);
invariant(
dataRouterContext,
Expand All @@ -1042,24 +1046,16 @@ function useScrollRestoration({
// Save positions on unload
useBeforeUnload(
React.useCallback(() => {
if (state?.navigation.state === "idle") {
let key =
(getKey ? getKey(state.location, state.matches) : null) ||
state.location.key;
if (navigation.state === "idle") {
let key = (getKey ? getKey(location, matches) : null) || location.key;
savedScrollPositions[key] = window.scrollY;
}
sessionStorage.setItem(
storageKey || SCROLL_RESTORATION_STORAGE_KEY,
JSON.stringify(savedScrollPositions)
);
window.history.scrollRestoration = "auto";
}, [
storageKey,
getKey,
state.navigation.state,
state.location,
state.matches,
])
}, [storageKey, getKey, navigation.state, location, matches])
);

// Read in any saved scroll locations
Expand Down
4 changes: 2 additions & 2 deletions packages/react-router/lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { Action as NavigationType } from "@remix-run/router";
// export from react-router
export interface RouteObject extends AgnosticRouteObject {
children?: RouteObject[];
element?: React.ReactNode;
errorElement?: React.ReactNode;
element?: React.ReactNode | null;
errorElement?: React.ReactNode | null;
}

export interface DataRouteObject extends RouteObject {
Expand Down
3 changes: 3 additions & 0 deletions packages/react-router/lib/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,9 @@ export function useMatches() {
() =>
matches.map((match) => {
let { pathname, params } = match;
// Note: This structure matches that created by createUseMatchesMatch
// in the @remix-run/router , so if you change this please also change
// that :) Eventually we'll DRY this up
return {
id: match.route.id,
pathname,
Expand Down
37 changes: 34 additions & 3 deletions packages/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
AgnosticRouteObject,
Submission,
SuccessResult,
AgnosticRouteMatch,
} from "./utils";
import {
DeferredData,
Expand Down Expand Up @@ -258,12 +259,20 @@ export interface RouterSubscriber {
(state: RouterState): void;
}

interface UseMatchesMatch {
id: string;
pathname: string;
params: AgnosticRouteMatch["params"];
data: unknown;
handle: unknown;
}

/**
* Function signature for determining the key to be used in scroll restoration
* for a given location
*/
export interface GetScrollRestorationKeyFunction {
(location: Location, matches: AgnosticDataRouteMatch[]): string | null;
(location: Location, matches: UseMatchesMatch[]): string | null;
}

/**
Expand Down Expand Up @@ -1626,7 +1635,10 @@ export function createRouter(init: RouterInit): Router {
matches: AgnosticDataRouteMatch[]
): void {
if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
let key = getScrollRestorationKey(location, matches) || location.key;
let userMatches = matches.map((m) =>
createUseMatchesMatch(m, state.loaderData)
);
let key = getScrollRestorationKey(location, userMatches) || location.key;
savedScrollPositions[key] = getScrollPosition();
}
}
Expand All @@ -1636,7 +1648,10 @@ export function createRouter(init: RouterInit): Router {
matches: AgnosticDataRouteMatch[]
): number | null {
if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
let key = getScrollRestorationKey(location, matches) || location.key;
let userMatches = matches.map((m) =>
createUseMatchesMatch(m, state.loaderData)
);
let key = getScrollRestorationKey(location, userMatches) || location.key;
let y = savedScrollPositions[key];
if (typeof y === "number") {
return y;
Expand Down Expand Up @@ -2674,6 +2689,22 @@ function hasNakedIndexQuery(search: string): boolean {
return new URLSearchParams(search).getAll("index").some((v) => v === "");
}

// Note: This should match the format exported by useMatches, so if you change
// this please also change that :) Eventually we'll DRY this up
function createUseMatchesMatch(
match: AgnosticDataRouteMatch,
loaderData: RouteData
): UseMatchesMatch {
let { route, pathname, params } = match;
return {
id: route.id,
pathname,
params,
data: loaderData[route.id] as unknown,
handle: route.handle as unknown,
};
}

function getTargetMatch(
matches: AgnosticDataRouteMatch[],
location: Location | string
Expand Down

0 comments on commit 5c8fdec

Please sign in to comment.