Skip to content

Commit 200eea9

Browse files
authored
feat: Allow pass-through script props in ScrollRestoration (#2879)
1 parent da0c278 commit 200eea9

File tree

4 files changed

+114
-4
lines changed

4 files changed

+114
-4
lines changed

.changeset/late-files-trade.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/react": patch
3+
---
4+
5+
Allow pass-through props to be passed to the script rendered by `ScrollRestoration`
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as React from "react";
2+
import { MemoryRouter, Outlet } from "react-router-dom";
3+
import { render, screen } from "@testing-library/react";
4+
5+
import { LiveReload, RemixEntryContext, Scripts } from "../components";
6+
import type { RemixEntryContextType } from "../components";
7+
import { ScrollRestoration } from "../scroll-restoration";
8+
9+
import "@testing-library/jest-dom/extend-expect";
10+
11+
function AppShell({ children }: { children: React.ReactNode }) {
12+
return (
13+
<React.Fragment>
14+
<Outlet />
15+
{children}
16+
<Scripts />
17+
<LiveReload />
18+
</React.Fragment>
19+
);
20+
}
21+
22+
describe("<ScrollRestoration />", () => {
23+
function withContext(stuff: JSX.Element) {
24+
let context: RemixEntryContextType = {
25+
routeModules: { idk: { default: () => null } },
26+
manifest: {
27+
routes: {
28+
idk: {
29+
hasLoader: true,
30+
hasAction: false,
31+
hasCatchBoundary: false,
32+
hasErrorBoundary: false,
33+
id: "idk",
34+
module: "idk",
35+
},
36+
},
37+
entry: { imports: [], module: "" },
38+
url: "",
39+
version: "",
40+
},
41+
matches: [],
42+
clientRoutes: [
43+
{
44+
id: "idk",
45+
path: "idk",
46+
hasLoader: true,
47+
element: "",
48+
module: "",
49+
async action() {
50+
return {};
51+
},
52+
async loader() {
53+
return {};
54+
},
55+
},
56+
],
57+
routeData: {},
58+
appState: {} as any,
59+
transitionManager: {
60+
getState() {
61+
return {
62+
transition: {},
63+
};
64+
},
65+
} as any,
66+
};
67+
return (
68+
<RemixEntryContext.Provider value={context}>
69+
<MemoryRouter>{stuff}</MemoryRouter>
70+
</RemixEntryContext.Provider>
71+
);
72+
}
73+
74+
it("should render a <script> tag", () => {
75+
render(
76+
withContext(
77+
<AppShell>
78+
<ScrollRestoration data-testid="scroll-script" />
79+
</AppShell>
80+
)
81+
);
82+
let script = screen.getByTestId("scroll-script");
83+
expect(script instanceof HTMLScriptElement).toBe(true);
84+
});
85+
86+
it("should pass props to <script>", () => {
87+
render(
88+
withContext(
89+
<AppShell>
90+
<ScrollRestoration
91+
data-testid="scroll-script"
92+
nonce="hello"
93+
crossOrigin="anonymous"
94+
/>
95+
</AppShell>
96+
)
97+
);
98+
let script = screen.getByTestId("scroll-script");
99+
expect(script).toHaveAttribute("nonce", "hello");
100+
expect(script).toHaveAttribute("crossorigin", "anonymous");
101+
});
102+
103+
it.todo("should restore scroll position");
104+
});

packages/remix-react/components.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ import type {
6565
////////////////////////////////////////////////////////////////////////////////
6666
// RemixEntry
6767

68-
interface RemixEntryContextType {
68+
export interface RemixEntryContextType {
6969
manifest: AssetsManifest;
7070
matches: BaseRouteMatch<ClientRoute>[];
7171
routeData: RouteData;
@@ -890,7 +890,7 @@ export function Meta() {
890890
*/
891891
let isHydrated = false;
892892

893-
type ScriptProps = Omit<
893+
export type ScriptProps = Omit<
894894
React.HTMLProps<HTMLScriptElement>,
895895
| "children"
896896
| "async"

packages/remix-react/scroll-restoration.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from "react";
22
import { useLocation } from "react-router-dom";
33

44
import { useBeforeUnload, useTransition } from "./components";
5+
import type { ScriptProps } from "./components";
56

67
let STORAGE_KEY = "positions";
78

@@ -20,7 +21,7 @@ if (typeof document !== "undefined") {
2021
*
2122
* @see https://remix.run/api/remix#scrollrestoration
2223
*/
23-
export function ScrollRestoration({ nonce = undefined }: { nonce?: string }) {
24+
export function ScrollRestoration(props: ScriptProps) {
2425
useScrollRestoration();
2526

2627
// wait for the browser to restore it on its own
@@ -54,7 +55,7 @@ export function ScrollRestoration({ nonce = undefined }: { nonce?: string }) {
5455

5556
return (
5657
<script
57-
nonce={nonce}
58+
{...props}
5859
suppressHydrationWarning
5960
dangerouslySetInnerHTML={{
6061
__html: `(${restoreScroll})(${JSON.stringify(STORAGE_KEY)})`,

0 commit comments

Comments
 (0)