Skip to content

Remove duplicate RouterProvider implementations #11679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thin-nails-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": minor
---

Remove duplicate `RouterProvider` impliementations
10 changes: 2 additions & 8 deletions packages/react-router/__tests__/data-memory-router-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import urlDataStrategy from "./router/utils/urlDataStrategy";
import { createDeferred } from "./router/utils/utils";
import MemoryNavigate from "./utils/MemoryNavigate";
import getHtml from "./utils/getHtml";
import { RouterProvider as DomRouterProvider } from "../lib/dom/lib";

describe("createMemoryRouter", () => {
let consoleWarn: jest.SpyInstance;
Expand Down Expand Up @@ -1193,9 +1192,7 @@ describe("createMemoryRouter", () => {
},
]);

// TODO: Fetchers only supported in DomRouterProvider at the moment, but
// that should be fixed once we align the two
render(<DomRouterProvider router={router} />);
render(<RouterProvider router={router} />);

await waitFor(() => screen.getByText("Fetch (1, empty)"));
fireEvent.click(screen.getByText("Fetch (1, empty)"));
Expand Down Expand Up @@ -1251,9 +1248,7 @@ describe("createMemoryRouter", () => {
},
]);

// TODO: Fetchers only supported in DomRouterProvider at the moment, but
// that should be fixed once we align the two
render(<DomRouterProvider router={router} />);
render(<RouterProvider router={router} />);

await waitFor(() => screen.getByText("Fetch (1, empty)"));
fireEvent.click(screen.getByText("Fetch (1, empty)"));
Expand Down Expand Up @@ -3371,7 +3366,6 @@ describe("createMemoryRouter", () => {
</React.Suspense>
);

console.log(getHtml(container));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<p>
Expand Down
290 changes: 290 additions & 0 deletions packages/react-router/__tests__/data-router-no-dom-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
/**
* @jest-environment node
*/

import * as React from "react";
import renderer from "react-test-renderer";
import { RouterProvider, useFetcher } from "../lib/dom/lib";
import { createMemoryRouter } from "../lib/components";
import { useLoaderData, useNavigate } from "../lib/hooks";

describe("RouterProvider works when no DOM APIs are available", () => {
it("renders and navigates", async () => {
let router = createMemoryRouter([
{
path: "/",
Component: () => {
let navigate = useNavigate();
return <button onClick={() => navigate("/foo")}>Go to /foo</button>;
},
},
{
path: "/foo",
loader: () => "FOO",
Component: () => {
let data = useLoaderData() as string;
return <h1>{data}</h1>;
},
},
]);
const component = renderer.create(<RouterProvider router={router} />);
let tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<button
onClick={[Function]}
>
Go to /foo
</button>
`);

await renderer.act(async () => {
// @ts-expect-error
tree.props.onClick();
await new Promise((resolve) => setTimeout(resolve, 0));
});

tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<h1>
FOO
</h1>
`);
});

it("is defensive against a view transition navigation", async () => {
let router = createMemoryRouter([
{
path: "/",
Component: () => {
let navigate = useNavigate();
return <button onClick={() => navigate("/foo")}>Go to /foo</button>;
},
},
{
path: "/foo",
loader: () => "FOO",
Component: () => {
let data = useLoaderData() as string;
return <h1>{data}</h1>;
},
},
]);
const component = renderer.create(<RouterProvider router={router} />);
let tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<button
onClick={[Function]}
>
Go to /foo
</button>
`);

let spy = jest.fn();
let unsubscribe = router.subscribe(spy);

await renderer.act(async () => {
router.navigate("/foo", {
unstable_viewTransition: true,
});
await new Promise((resolve) => setTimeout(resolve, 0));
});

tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<h1>
FOO
</h1>
`);

expect(spy.mock.calls[0][0].location.pathname).toBe("/");
expect(spy.mock.calls[0][0].navigation.state).toBe("loading");
expect(spy.mock.calls[0][0].navigation.location.pathname).toBe("/foo");
expect(spy.mock.calls[0][1].unstable_viewTransitionOpts).toBeUndefined();

expect(spy.mock.calls[1][0].location.pathname).toBe("/foo");
expect(spy.mock.calls[1][0].navigation.state).toBe("idle");
expect(spy.mock.calls[1][1].unstable_viewTransitionOpts).toEqual({
currentLocation: {
hash: "",
key: "default",
pathname: "/",
search: "",
state: null,
},
nextLocation: {
hash: "",
key: expect.any(String),
pathname: "/foo",
search: "",
state: null,
},
});

unsubscribe();
});

it("is defensive against a flushSync navigation", async () => {
let router = createMemoryRouter([
{
path: "/",
Component: () => {
let navigate = useNavigate();
return <button onClick={() => navigate("/foo")}>Go to /foo</button>;
},
},
{
path: "/foo",
loader: () => "FOO",
Component: () => {
let data = useLoaderData() as string;
return <h1>{data}</h1>;
},
},
]);
const component = renderer.create(<RouterProvider router={router} />);
let tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<button
onClick={[Function]}
>
Go to /foo
</button>
`);

let spy = jest.fn();
let unsubscribe = router.subscribe(spy);

await renderer.act(async () => {
router.navigate("/foo", {
unstable_flushSync: true,
});
await new Promise((resolve) => setTimeout(resolve, 0));
});

tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<h1>
FOO
</h1>
`);

expect(spy.mock.calls[0][0].location.pathname).toBe("/");
expect(spy.mock.calls[0][0].navigation.state).toBe("loading");
expect(spy.mock.calls[0][0].navigation.location.pathname).toBe("/foo");
expect(spy.mock.calls[0][1].unstable_flushSync).toBe(true);

expect(spy.mock.calls[1][0].location.pathname).toBe("/foo");
expect(spy.mock.calls[1][0].navigation.state).toBe("idle");
expect(spy.mock.calls[1][1].unstable_flushSync).toBe(false);

unsubscribe();
});

it("supports fetcher loads", async () => {
let router = createMemoryRouter([
{
path: "/",
Component: () => {
let fetcher = useFetcher();
return (
<button onClick={() => fetcher.load("/fetch")}>
Load fetcher
{fetcher.data || ""}
</button>
);
},
},
{
path: "/fetch",
loader() {
return "LOADER";
},
},
]);
const component = renderer.create(<RouterProvider router={router} />);
let tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<button
onClick={[Function]}
>
Load fetcher
</button>
`);

await renderer.act(async () => {
// @ts-expect-error
tree.props.onClick();
await new Promise((resolve) => setTimeout(resolve, 100));
});

tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<button
onClick={[Function]}
>
Load fetcher
LOADER
</button>
`);
});

it("supports fetcher submissions", async () => {
let router = createMemoryRouter([
{
path: "/",
Component: () => {
let fetcher = useFetcher();
return (
<button
onClick={() =>
fetcher.submit(
{ message: "echo" },
{
method: "post",
action: "/fetch",
encType: "application/json",
}
)
}
>
Submit fetcher
{fetcher.data?.message || ""}
</button>
);
},
},
{
path: "/fetch",
async action({ request }) {
let data = await request.json();
return { message: data.message.toUpperCase() };
},
},
]);
const component = renderer.create(<RouterProvider router={router} />);
let tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<button
onClick={[Function]}
>
Submit fetcher
</button>
`);

await renderer.act(async () => {
// @ts-expect-error
tree.props.onClick();
await new Promise((resolve) => setTimeout(resolve, 100));
});

tree = component.toJSON();
expect(tree).toMatchInlineSnapshot(`
<button
onClick={[Function]}
>
Submit fetcher
ECHO
</button>
`);
});
});
6 changes: 2 additions & 4 deletions packages/react-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import type {
PathRouteProps,
RouteProps,
RouterProps,
RouterProviderProps,
RoutesProps,
} from "./lib/components";
import {
Expand All @@ -67,7 +66,6 @@ import {
Outlet,
Route,
Router,
RouterProvider,
Routes,
createRoutesFromChildren,
renderMatches,
Expand Down Expand Up @@ -168,7 +166,6 @@ export type {
RouteObject,
RouteProps,
RouterProps,
RouterProviderProps,
RoutesProps,
Search,
ShouldRevalidateFunction,
Expand All @@ -188,7 +185,6 @@ export {
Outlet,
Route,
Router,
RouterProvider,
Routes,
createMemoryRouter,
createPath,
Expand Down Expand Up @@ -279,6 +275,7 @@ export type {
SubmitFunction,
FetcherSubmitFunction,
FetcherWithComponents,
RouterProviderProps,
} from "./lib/dom/lib";
export {
createBrowserRouter,
Expand All @@ -293,6 +290,7 @@ export {
unstable_HistoryRouter,
NavLink,
Form,
RouterProvider,
ScrollRestoration,
useLinkClickHandler,
useSearchParams,
Expand Down
Loading