From 2627aeae5e5821a30707896ff4d4a71199ca85e0 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 2 May 2023 11:42:57 -0400 Subject: [PATCH] Fix usage of Component API within descendant routes --- .changeset/descendant-routes-component.md | 5 +++++ docs/route/route.md | 4 +++- .../react-router/__tests__/Route-test.tsx | 19 +++++++++++++++++++ packages/react-router/lib/hooks.tsx | 8 ++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .changeset/descendant-routes-component.md diff --git a/.changeset/descendant-routes-component.md b/.changeset/descendant-routes-component.md new file mode 100644 index 0000000000..c4149968d9 --- /dev/null +++ b/.changeset/descendant-routes-component.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Fix usage of `Component` API within descendant `` diff --git a/docs/route/route.md b/docs/route/route.md index db46a65331..8201f3192d 100644 --- a/docs/route/route.md +++ b/docs/route/route.md @@ -62,7 +62,7 @@ const router = createBrowserRouter( Neither style is discouraged and behavior is identical. For the majority of this doc we will use the JSX style because that's what most people are accustomed to in the context of React Router. -If you do not wish to specify a React element (i.e., `element={}`) you may specify a `Component` instead (i.e., `Component={MyComponent}`) and React Router will call `createElement` for you internally. +When using `RouterProvider`, if you do not wish to specify a React element (i.e., `element={}`) you may specify a `Component` instead (i.e., `Component={MyComponent}`) and React Router will call `createElement` for you internally. You should only do this for `RouterProvider` applications though since using `Component` inside of `` will de-optimize React's ability to reuse the created element across renders. ## Type declaration @@ -312,6 +312,8 @@ Otherwise use `Component` and React Router will create the React Element for you ``` +You should only opt into the `Component` API for data routes via `RouterProvider`. Using this API on a `` inside `` will de-optimize React's ability to reuse the created element across renders. + ## `errorElement`/`ErrorBoundary` When a route throws an exception while rendering, in a `loader` or in an `action`, this React Element/Component will render instead of the normal `element`/`Component`. diff --git a/packages/react-router/__tests__/Route-test.tsx b/packages/react-router/__tests__/Route-test.tsx index 3c7b3611c5..ff5a98c8c9 100644 --- a/packages/react-router/__tests__/Route-test.tsx +++ b/packages/react-router/__tests__/Route-test.tsx @@ -22,6 +22,25 @@ describe("A ", () => { `); }); + it("renders its `Component` prop", () => { + let renderer: TestRenderer.ReactTestRenderer; + TestRenderer.act(() => { + renderer = TestRenderer.create( + + +

Home

} /> +
+
+ ); + }); + + expect(renderer.toJSON()).toMatchInlineSnapshot(` +

+ Home +

+ `); + }); + it("renders its child routes when no `element` prop is given", () => { let renderer: TestRenderer.ReactTestRenderer; TestRenderer.act(() => { diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index be846ef51a..f218512279 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -689,6 +689,14 @@ export function _renderMatches( let children: React.ReactNode; if (error) { children = errorElement; + } else if (match.route.Component) { + // Note: This is a de-optimized path since React won't re-use the + // ReactElement since it's identity changes with each new + // React.createElement call. We keep this so folks can use + // `` in `` but generally `Component` + // usage is only advised in `RouterProvider` when we can convert it to + // `element` ahead of time. + children = ; } else if (match.route.element) { children = match.route.element; } else {