diff --git a/.changeset/cool-cameras-count.md b/.changeset/cool-cameras-count.md
new file mode 100644
index 0000000000..02c8ffc2bd
--- /dev/null
+++ b/.changeset/cool-cameras-count.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-app-api': patch
+---
+
+When using React Router v6 stable, it is now possible for components within the `Route` element tree to have `path` props, although they will be ignored.
diff --git a/packages/core-app-api/src/routing/collectors.stable.test.tsx b/packages/core-app-api/src/routing/collectors.stable.test.tsx
index 2324ea5b69..f8f71a1a9a 100644
--- a/packages/core-app-api/src/routing/collectors.stable.test.tsx
+++ b/packages/core-app-api/src/routing/collectors.stable.test.tsx
@@ -453,18 +453,19 @@ describe('discovery', () => {
);
});
- it('should throw elements within element prop contains a path', () => {
- expect(() => {
- traverseElementTree({
- root: } />,
- discoverers: [childDiscoverer, routeElementDiscoverer],
- collectors: {
- routing: routingV2Collector,
- },
- });
- }).toThrow(
- 'Elements within the element prop tree may not have paths, found "bar"',
- );
+ it('should ignore path props within route elements', () => {
+ const { routing } = traverseElementTree({
+ root: } />,
+ discoverers: [childDiscoverer, routeElementDiscoverer],
+ collectors: {
+ routing: routingV2Collector,
+ },
+ });
+ expect(sortedEntries(routing.paths)).toEqual([[ref1, 'foo']]);
+ expect(sortedEntries(routing.parents)).toEqual([[ref1, undefined]]);
+ expect(routing.objects).toEqual([
+ routeObj('foo', [ref1], [], undefined, plugin),
+ ]);
});
it('should throw when a routable extension does not have a path set', () => {
diff --git a/packages/core-app-api/src/routing/collectors.tsx b/packages/core-app-api/src/routing/collectors.tsx
index e5d71f5117..815ae534f1 100644
--- a/packages/core-app-api/src/routing/collectors.tsx
+++ b/packages/core-app-api/src/routing/collectors.tsx
@@ -53,6 +53,8 @@ interface RoutingV2CollectorContext {
isElementAncestor?: boolean;
}
+// This collects all the mount points and their plugins within an element tree.
+// Unlike regular traversal this ignores all other things, like path props and mount point gatherers.
function collectSubTree(
node: ReactNode,
entries = new Array<{ routeRef: RouteRef; plugin?: BackstagePlugin }>(),
@@ -62,12 +64,6 @@ function collectSubTree(
return;
}
- if (element.props.path) {
- throw new Error(
- `Elements within the element prop tree may not have paths, found "${element.props.path}"`,
- );
- }
-
const routeRef = getComponentData(element, 'core.mountPoint');
if (routeRef) {
const plugin = getComponentData(element, 'core.plugin');
@@ -87,6 +83,16 @@ export const routingV2Collector = createCollector(
objects: new Array(),
}),
(acc, node, parent, ctx?: RoutingV2CollectorContext) => {
+ // If we're in an element prop, ignore everything
+ if (ctx?.isElementAncestor) {
+ return ctx;
+ }
+
+ // Start ignoring everything if we enter an element prop
+ if (parent?.props.element === node) {
+ return { ...ctx, isElementAncestor: true };
+ }
+
const pathProp: unknown = node.props?.path;
const mountPoint = getComponentData(node, 'core.mountPoint');
@@ -98,15 +104,6 @@ export const routingV2Collector = createCollector(
);
}
- // If we're in an element prop, ignore everything
- if (ctx?.isElementAncestor) {
- return ctx;
- }
- // Start ignoring everything if we enter an element prop
- if (parent?.props.element === node) {
- return { ...ctx, isElementAncestor: true };
- }
-
const parentChildren = ctx?.obj?.children ?? acc.objects;
if (pathProp !== undefined) {