Skip to content

Commit 4c190e3

Browse files
authored
fix: move fallbackElement into router context (#9167)
1 parent 5c8fdec commit 4c190e3

File tree

4 files changed

+128
-44
lines changed

4 files changed

+128
-44
lines changed

packages/react-router-dom/__tests__/data-browser-router-test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
useFetchers,
3232
UNSAFE_DataRouterStateContext as DataRouterStateContext,
3333
defer,
34+
useLocation,
3435
} from "react-router-dom";
3536

3637
// Private API
@@ -330,6 +331,54 @@ function testDomRouter(
330331
`);
331332
});
332333

334+
it("renders fallbackElement within router contexts", async () => {
335+
let fooDefer = createDeferred();
336+
let { container } = render(
337+
<TestDataRouter
338+
window={getWindow("/foo")}
339+
fallbackElement={<FallbackElement />}
340+
>
341+
<Route path="/" element={<Outlet />}>
342+
<Route
343+
path="foo"
344+
loader={() => fooDefer.promise}
345+
element={<Foo />}
346+
/>
347+
</Route>
348+
</TestDataRouter>
349+
);
350+
351+
function FallbackElement() {
352+
let location = useLocation();
353+
return <p>Loading{location.pathname}</p>;
354+
}
355+
356+
function Foo() {
357+
let data = useLoaderData();
358+
return <h1>Foo:{data?.message}</h1>;
359+
}
360+
361+
expect(getHtml(container)).toMatchInlineSnapshot(`
362+
"<div>
363+
<p>
364+
Loading
365+
/foo
366+
</p>
367+
</div>"
368+
`);
369+
370+
fooDefer.resolve({ message: "From Foo Loader" });
371+
await waitFor(() => screen.getByText("Foo:From Foo Loader"));
372+
expect(getHtml(container)).toMatchInlineSnapshot(`
373+
"<div>
374+
<h1>
375+
Foo:
376+
From Foo Loader
377+
</h1>
378+
</div>"
379+
`);
380+
});
381+
333382
it("handles link navigations", async () => {
334383
render(
335384
<TestDataRouter window={getWindow("/foo")} hydrationData={{}}>

packages/react-router-dom/index.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,8 @@ export function DataBrowserRouter({
236236
let router = routerSingleton;
237237

238238
return (
239-
<DataRouterProvider
240-
router={router}
241-
basename={basename}
242-
fallbackElement={fallbackElement}
243-
>
244-
<DataRouter />
239+
<DataRouterProvider router={router} basename={basename}>
240+
<DataRouter fallbackElement={fallbackElement} />
245241
</DataRouterProvider>
246242
);
247243
}
@@ -276,12 +272,8 @@ export function DataHashRouter({
276272
let router = routerSingleton;
277273

278274
return (
279-
<DataRouterProvider
280-
router={router}
281-
basename={basename}
282-
fallbackElement={fallbackElement}
283-
>
284-
<DataRouter />
275+
<DataRouterProvider router={router} basename={basename}>
276+
<DataRouter fallbackElement={fallbackElement} />
285277
</DataRouterProvider>
286278
);
287279
}

packages/react-router/__tests__/data-memory-router-test.tsx

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ import "@testing-library/jest-dom";
1111
import type { FormMethod, Router } from "@remix-run/router";
1212
import { createMemoryRouter } from "@remix-run/router";
1313

14-
import {
15-
DataMemoryRouterProps,
16-
UNSAFE_DataRouter as DataRouter,
17-
UNSAFE_DataRouterProvider as DataRouterProvider,
18-
} from "react-router";
14+
import type { DataMemoryRouterProps } from "react-router";
1915
import {
2016
DataMemoryRouter,
2117
Await,
@@ -26,14 +22,17 @@ import {
2622
useAsyncError,
2723
useAsyncValue,
2824
useLoaderData,
25+
useLocation,
2926
useMatches,
3027
useRouteLoaderData,
3128
useRouteError,
3229
useNavigation,
3330
useRevalidator,
3431
MemoryRouter,
3532
Routes,
33+
UNSAFE_DataRouter as DataRouter,
3634
UNSAFE_DataRouterContext as DataRouterContext,
35+
UNSAFE_DataRouterProvider as DataRouterProvider,
3736
} from "react-router";
3837

3938
// Private API
@@ -301,6 +300,54 @@ describe("<DataMemoryRouter>", () => {
301300
`);
302301
});
303302

303+
it("renders fallbackElement within router contexts", async () => {
304+
let fooDefer = createDeferred();
305+
let { container } = render(
306+
<DataMemoryRouter
307+
fallbackElement={<FallbackElement />}
308+
initialEntries={["/foo"]}
309+
>
310+
<Route path="/" element={<Outlet />}>
311+
<Route path="foo" loader={() => fooDefer.promise} element={<Foo />} />
312+
</Route>
313+
</DataMemoryRouter>
314+
);
315+
316+
function FallbackElement() {
317+
let location = useLocation();
318+
return (
319+
<>
320+
<p>Loading{location.pathname}</p>
321+
</>
322+
);
323+
}
324+
325+
function Foo() {
326+
let data = useLoaderData();
327+
return <h1>Foo:{data?.message}</h1>;
328+
}
329+
330+
expect(getHtml(container)).toMatchInlineSnapshot(`
331+
"<div>
332+
<p>
333+
Loading
334+
/foo
335+
</p>
336+
</div>"
337+
`);
338+
339+
fooDefer.resolve({ message: "From Foo Loader" });
340+
await waitFor(() => screen.getByText("Foo:From Foo Loader"));
341+
expect(getHtml(container)).toMatchInlineSnapshot(`
342+
"<div>
343+
<h1>
344+
Foo:
345+
From Foo Loader
346+
</h1>
347+
</div>"
348+
`);
349+
});
350+
304351
it("handles link navigations", async () => {
305352
render(
306353
<DataMemoryRouter initialEntries={["/foo"]} hydrationData={{}}>

packages/react-router/lib/components.tsx

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,21 @@ export function _resetModuleScope() {
5858
routerSingleton = null;
5959
}
6060

61+
interface DataRouterProviderProps {
62+
basename?: string;
63+
children?: React.ReactNode;
64+
router: RemixRouter;
65+
}
66+
6167
/**
6268
* A higher-order component that, given a Remix Router instance. setups the
6369
* Context's required for data routing
6470
*/
6571
export function DataRouterProvider({
6672
basename,
6773
children,
68-
fallbackElement,
6974
router,
70-
}: {
71-
basename?: string;
72-
children?: React.ReactNode;
73-
fallbackElement?: React.ReactNode;
74-
router: RemixRouter;
75-
}): React.ReactElement {
75+
}: DataRouterProviderProps): React.ReactElement {
7676
// Sync router state to our component state to force re-renders
7777
let state: RouterState = useSyncExternalStoreShim(
7878
router.subscribe,
@@ -98,29 +98,29 @@ export function DataRouterProvider({
9898
};
9999
}, [router]);
100100

101-
let dataRouterContext: DataRouterContextObject = {
102-
router,
103-
navigator,
104-
static: false,
105-
basename: basename || "/",
106-
};
107-
108-
if (!state.initialized) {
109-
return <>{fallbackElement}</>;
110-
}
111-
112101
return (
113-
<DataRouterContext.Provider value={dataRouterContext}>
102+
<DataRouterContext.Provider
103+
value={{
104+
router,
105+
navigator,
106+
static: false,
107+
basename: basename || "/",
108+
}}
109+
>
114110
<DataRouterStateContext.Provider value={state} children={children} />
115111
</DataRouterContext.Provider>
116112
);
117113
}
118114

115+
interface DataRouterProps {
116+
fallbackElement?: React.ReactNode;
117+
}
118+
119119
/**
120120
* A data-aware wrapper for `<Router>` that leverages the Context's provided by
121121
* `<DataRouterProvider>`
122122
*/
123-
export function DataRouter() {
123+
export function DataRouter({ fallbackElement }: DataRouterProps) {
124124
let dataRouterContext = React.useContext(DataRouterContext);
125125
invariant(
126126
dataRouterContext,
@@ -135,7 +135,7 @@ export function DataRouter() {
135135
navigationType={router.state.historyAction}
136136
navigator={navigator}
137137
>
138-
<Routes />
138+
{router.state.initialized ? <Routes /> : fallbackElement}
139139
</Router>
140140
);
141141
}
@@ -173,12 +173,8 @@ export function DataMemoryRouter({
173173
let router = routerSingleton;
174174

175175
return (
176-
<DataRouterProvider
177-
router={router}
178-
basename={basename}
179-
fallbackElement={fallbackElement}
180-
>
181-
<DataRouter />
176+
<DataRouterProvider router={router} basename={basename}>
177+
<DataRouter fallbackElement={fallbackElement} />
182178
</DataRouterProvider>
183179
);
184180
}

0 commit comments

Comments
 (0)