Skip to content

Commit

Permalink
Reduce RouterProvider re-renders when using View Transitions (#11803)
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 authored Jul 17, 2024
1 parent 56d0b4b commit a252428
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-worms-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router-dom": patch
---

Memoize some `RouterProvider` internals to reduce uneccesary re-renders
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"none": "17.3 kB"
},
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
"none": "17.2 kB"
"none": "17.3 kB"
},
"packages/react-router-dom/dist/umd/react-router-dom.production.min.js": {
"none": "23.6 kB"
Expand Down
79 changes: 77 additions & 2 deletions packages/react-router-dom/__tests__/data-browser-router-test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ErrorResponse, Fetcher } from "@remix-run/router";
import type { ErrorResponse, Fetcher, RouterState } from "@remix-run/router";
import "@testing-library/jest-dom";
import {
act,
Expand Down Expand Up @@ -37,7 +37,7 @@ import {
} from "react-router-dom";

import getHtml from "../../react-router/__tests__/utils/getHtml";
import { createDeferred } from "../../router/__tests__/utils/utils";
import { createDeferred, tick } from "../../router/__tests__/utils/utils";

testDomRouter("<DataBrowserRouter>", createBrowserRouter, (url) =>
getWindowImpl(url, false)
Expand Down Expand Up @@ -7465,6 +7465,81 @@ function testDomRouter(
await waitFor(() => screen.getByText("D"));
expect(spy).toHaveBeenCalledTimes(2);
});

it("Does not cause extra re-renders due to ViewTransitionContext updates", async () => {
let testWindow = getWindow("/");
testWindow.document.startViewTransition = (cb) => {
cb();
return {
ready: Promise.resolve(),
finished: Promise.resolve(),
updateCallbackDone: Promise.resolve(),
skipTransition: () => {},
};
};

let renders: RouterState[] = [];
let router = createTestRouter(
[
{
path: "/",
Component() {
return (
<>
<Link to="/page" unstable_viewTransition>
/page
</Link>
<Outlet />
</>
);
},
children: [
{
index: true,
async loader() {
await tick();
return "INDEX";
},
Component() {
renders.push(useLocation(), useNavigation());
return <h1>{useLoaderData()}</h1>;
},
},
{
path: "page",
async loader() {
await tick();
return "PAGE";
},
Component() {
renders.push(useLocation(), useNavigation());
return <h1>{useLoaderData()}</h1>;
},
},
],
},
],
{ window: testWindow }
);
render(<RouterProvider router={router} />);
await waitFor(() => screen.getByText("INDEX"));

renders = [];
fireEvent.click(screen.getByText("/page"));
await waitFor(() => screen.getByText("PAGE"));

expect(renders).toMatchObject([
// Re-render of current location with navigation.state = "loading"
{ pathname: "/" },
{
state: "loading",
location: { pathname: "/page" },
},
// Render of new location with navigation.state = "idle"
{ pathname: "/page" },
{ state: "idle" },
]);
});
});
});
}
Expand Down
17 changes: 13 additions & 4 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
Navigator,
RelativeRoutingType,
RouteObject,
RouterProps,
RouterProviderProps,
To,
unstable_PatchRoutesOnMissFunction,
Expand Down Expand Up @@ -708,6 +709,13 @@ export function RouterProvider({
[router, navigator, basename]
);

let routerFuture = React.useMemo<RouterProps["future"]>(
() => ({
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
}),
[router.future.v7_relativeSplatPath]
);

// The fragment and {null} here are important! We need them to keep React 18's
// useId happy when we are server-rendering since we may have a <script> here
// containing the hydrated server-side staticContext (from StaticRouterProvider).
Expand All @@ -725,12 +733,10 @@ export function RouterProvider({
location={state.location}
navigationType={state.historyAction}
navigator={navigator}
future={{
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
}}
future={routerFuture}
>
{state.initialized || router.future.v7_partialHydration ? (
<DataRoutes
<MemoizedDataRoutes
routes={router.routes}
future={router.future}
state={state}
Expand All @@ -748,6 +754,9 @@ export function RouterProvider({
);
}

// Memoize to avoid re-renders when updating `ViewTransitionContext`
const MemoizedDataRoutes = React.memo(DataRoutes);

function DataRoutes({
routes,
future,
Expand Down

0 comments on commit a252428

Please sign in to comment.