diff --git a/.changeset/tall-trains-remain.md b/.changeset/tall-trains-remain.md new file mode 100644 index 0000000000..9b3a44d037 --- /dev/null +++ b/.changeset/tall-trains-remain.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Updated `FlatRoutes` to be compatible with `react-router` v6 stable. diff --git a/packages/core-app-api/src/routing/FlatRoutes.tsx b/packages/core-app-api/src/routing/FlatRoutes.tsx index 02dbc301fb..5688b2093e 100644 --- a/packages/core-app-api/src/routing/FlatRoutes.tsx +++ b/packages/core-app-api/src/routing/FlatRoutes.tsx @@ -18,6 +18,8 @@ import React, { ReactNode } from 'react'; import { useRoutes } from 'react-router-dom'; import { useApp, useElementFilter } from '@backstage/core-plugin-api'; +let warned = false; + type RouteObject = { path: string; element: ReactNode; @@ -49,7 +51,11 @@ export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { const { NotFoundErrorPage } = app.getComponents(); const routes = useElementFilter(props.children, elements => elements - .getElements<{ path?: string; children: ReactNode }>() + .getElements<{ + path?: string; + element?: ReactNode; + children?: ReactNode; + }>() .flatMap(child => { let path = child.props.path; @@ -59,16 +65,30 @@ export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { } path = path?.replace(/\/\*$/, '') ?? '/'; + let element = child.props.element; + if (!element) { + element = child; + if (!warned && process.env.NODE_ENV !== 'test') { + // eslint-disable-next-line no-console + console.warn( + 'DEPRECATION WARNING: All elements within must be of type with an element prop. ' + + 'Existing usages of should be replaced with } />', + ); + warned = true; + } + } + return [ { + // Each route matches any sub route, except for the explicit root path path, - element: child, + element, children: child.props.children ? [ // These are the children of each route, which we all add in under a catch-all // subroute in order to make them available to `useOutlet` { - path: path === '/' ? '/' : '/*', // The root path must require an exact match + path: path === '/' ? '/' : '*', // The root path must require an exact match element: child.props.children, }, ] @@ -77,19 +97,19 @@ export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { ]; }) // Routes are sorted to work around a bug where prefixes are unexpectedly matched + // TODO(Rugvip): This can be removed once react-router v6 beta is no longer supported .sort((a, b) => b.path.localeCompare(a.path)) - // We make sure all routes have '/*' appended, except '/' - .map(obj => { - obj.path = obj.path === '/' ? '/' : `${obj.path}/*`; - return obj; - }), + .map(obj => ({ ...obj, path: obj.path === '/' ? '/' : `${obj.path}/*` })), ); // TODO(Rugvip): Possibly add a way to skip this, like a noNotFoundPage prop - routes.push({ - element: , - path: '/*', - }); + const withNotFound = [ + ...routes, + { + path: '/*', + element: , + }, + ]; - return useRoutes(routes); + return useRoutes(withNotFound); };