@@ -9,6 +9,9 @@ import type { Fixture, AppFixture } from "./helpers/create-fixture.js";
99import { PlaywrightFixture } from "./helpers/playwright-fixture.js" ;
1010import { createProject , viteBuild } from "./helpers/vite.js" ;
1111
12+ // SSR'd useId value we can assert against pre- and post-hydration
13+ const USE_ID_VALUE = ":R1:" ;
14+
1215test . describe ( "SPA Mode" , ( ) => {
1316 let fixture : Fixture ;
1417 let appFixture : AppFixture ;
@@ -26,9 +29,11 @@ test.describe("SPA Mode", () => {
2629 });
2730 ` ,
2831 "app/root.tsx" : js `
32+ import * as React from "react";
2933 import { Form, Link, Links, Meta, Outlet, Scripts } from "@remix-run/react";
3034
3135 export default function Root() {
36+ let id = React.useId();
3237 return (
3338 <html lang="en">
3439 <head>
@@ -37,6 +42,7 @@ test.describe("SPA Mode", () => {
3742 </head>
3843 <body>
3944 <h1 data-root>Root</h1>
45+ <pre data-use-id>{id}</pre>
4046 <nav>
4147 <Link to="/about">/about</Link>
4248 <br/>
@@ -66,6 +72,10 @@ test.describe("SPA Mode", () => {
6672 }
6773
6874 export function HydrateFallback() {
75+ const id = React.useId();
76+ const [hydrated, setHydrated] = React.useState(false);
77+ React.useEffect(() => setHydrated(true), []);
78+
6979 return (
7080 <html lang="en">
7181 <head>
@@ -74,14 +84,16 @@ test.describe("SPA Mode", () => {
7484 </head>
7585 <body>
7686 <h1 data-loading>Loading SPA...</h1>
87+ <pre data-use-id>{id}</pre>
88+ {hydrated ? <h3 data-hydrated>Hydrated</h3> : null}
7789 <Scripts />
7890 </body>
7991 </html>
8092 );
8193 }
82- ` ,
94+ ` ,
8395 "app/routes/_index.tsx" : js `
84- import { useState, useEffect } from "react";
96+ import * as React from "react";
8597 import { useLoaderData } from "@remix-run/react";
8698
8799 export function meta({ data }) {
@@ -90,14 +102,17 @@ test.describe("SPA Mode", () => {
90102 }];
91103 }
92104
93- export function clientLoader() {
105+ export async function clientLoader({ request }) {
106+ if (new URL(request.url).searchParams.has('slow')) {
107+ await new Promise(r => setTimeout(r, 500));
108+ }
94109 return "Index Loader Data";
95110 }
96111
97112 export default function Component() {
98113 let data = useLoaderData();
99- const [mounted, setMounted] = useState(false);
100- useEffect(() => setMounted(true), []);
114+ const [mounted, setMounted] = React. useState(false);
115+ React. useEffect(() => setMounted(true), []);
101116
102117 return (
103118 <>
@@ -159,7 +174,7 @@ test.describe("SPA Mode", () => {
159174 let error = useRouteError();
160175 return <pre data-error>{error.data}</pre>
161176 }
162- ` ,
177+ ` ,
163178 } ,
164179 } ) ;
165180
@@ -241,6 +256,9 @@ test.describe("SPA Mode", () => {
241256 expect ( await page . locator ( "[data-loading]" ) . textContent ( ) ) . toBe (
242257 "Loading SPA..."
243258 ) ;
259+ expect ( await page . locator ( "[data-use-id]" ) . textContent ( ) ) . toBe (
260+ USE_ID_VALUE
261+ ) ;
244262 expect ( await page . locator ( "title" ) . textContent ( ) ) . toBe (
245263 "Index Title: undefined"
246264 ) ;
@@ -263,6 +281,25 @@ test.describe("SPA Mode", () => {
263281 ) ;
264282 } ) ;
265283
284+ test ( "hydrates a proper useId value" , async ( { page } ) => {
285+ let app = new PlaywrightFixture ( appFixture , page ) ;
286+ await app . goto ( "/?slow" ) ;
287+
288+ // We should hydrate the same useId value in HydrateFallback that we
289+ // rendered on the server above
290+ await page . waitForSelector ( "[data-hydrated]" ) ;
291+ expect ( await page . locator ( "[data-use-id]" ) . textContent ( ) ) . toBe (
292+ USE_ID_VALUE
293+ ) ;
294+
295+ // Once hydrated, we should get a different useId value from the root component
296+ await page . waitForSelector ( "[data-route]" ) ;
297+ expect ( await page . locator ( "[data-route]" ) . textContent ( ) ) . toBe ( "Index" ) ;
298+ expect ( await page . locator ( "[data-use-id]" ) . textContent ( ) ) . not . toBe (
299+ USE_ID_VALUE
300+ ) ;
301+ } ) ;
302+
266303 test ( "navigates and calls loaders" , async ( { page } ) => {
267304 let app = new PlaywrightFixture ( appFixture , page ) ;
268305 await app . goto ( "/" ) ;
0 commit comments