Skip to content
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

Fix usage of Component API within descendant routes #10434

Merged
merged 1 commit into from
May 2, 2023
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/descendant-routes-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": patch
---

Fix usage of `Component` API within descendant `<Routes>`
4 changes: 3 additions & 1 deletion docs/route/route.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<docs-info>If you do not wish to specify a React element (i.e., `element={<MyComponent />}`) you may specify a `Component` instead (i.e., `Component={MyComponent}`) and React Router will call `createElement` for you internally.</docs-info>
<docs-info>When using `RouterProvider`, if you do not wish to specify a React element (i.e., `element={<MyComponent />}`) 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 `<Routes>` will de-optimize React's ability to reuse the created element across renders.</docs-info>

## Type declaration

Expand Down Expand Up @@ -312,6 +312,8 @@ Otherwise use `Component` and React Router will create the React Element for you
<Route path="/for-sale" Component={Properties} />
```

<docs-warning>You should only opt into the `Component` API for data routes via `RouterProvider`. Using this API on a `<Route>` inside `<Routes>` will de-optimize React's ability to reuse the created element across renders.</docs-warning>

## `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`.
Expand Down
19 changes: 19 additions & 0 deletions packages/react-router/__tests__/Route-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ describe("A <Route>", () => {
`);
});

it("renders its `Component` prop", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="home" Component={() => <h1>Home</h1>} />
</Routes>
</MemoryRouter>
);
});

expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
Home
</h1>
`);
});

it("renders its child routes when no `element` prop is given", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
Expand Down
8 changes: 8 additions & 0 deletions packages/react-router/lib/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
// `<Route Component={...}>` in `<Routes>` but generally `Component`
// usage is only advised in `RouterProvider` when we can convert it to
// `element` ahead of time.
children = <match.route.Component />;
} else if (match.route.element) {
children = match.route.element;
} else {
Expand Down