From 9f949e27ba57cb86d71a6ca6f1bb4c625aced087 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 12 Jan 2024 17:03:22 +0100 Subject: [PATCH] docs(routing): try to reduce code snippets Signed-off-by: Camila Belo --- .../frontend-system/architecture/07-routes.md | 664 +++++------------- 1 file changed, 191 insertions(+), 473 deletions(-) diff --git a/docs/frontend-system/architecture/07-routes.md b/docs/frontend-system/architecture/07-routes.md index c1fd424ae3..0aa6980713 100644 --- a/docs/frontend-system/architecture/07-routes.md +++ b/docs/frontend-system/architecture/07-routes.md @@ -10,46 +10,38 @@ description: Frontend routes ## Introduction -This page describes the frontend system that helps bring together content from a multitude of plugins into one Backstage application. +In Backstage, plug-in boundaries and connections must be clearly defined. The app should allow navigation between plug-ins while isolating faults within them. -The core principle of the frontend system is that plugins should have clear boundaries and connections. It should isolate crashes within a plugin, but allow navigation between them. It should allow for plugins to be loaded only when needed, and enable plugins to provide extension points for other plugins to build upon. The frontend system is also built with an app-first mindset, prioritizing simplicity and clarity in the app over that in the plugins and core APIs. - -The frontend system isn't a single API surface. It is a collection of patterns, primitives, and APIs. At the core is the concept of extensions, which are exported by plugins for use in the app. One of the concepts is `RouteRef` which enables us route between pages in a flexible way, and it is especially important when bringing together different open source plugins. +In order to address the problem outlined above, we introduced the concept of `RouteRef` which enables us route between pages in a flexible way, and it is especially important when bringing together different open source plugins. ## Route References -In order to address the problem outlined above, we introduced the concept of route references. A `RouteRef` is an abstract path in a Backstage app, and these paths can be configured both at plugin level (by plugin developers) and at the app level (by integrators). +A `RouteRef` is an abstract path in a Backstage app, and these paths can be configured both at plugin level (by plugin developers) and at the app level (by integrators). Plugin developers create a `RouteRef` to expose a path in Backstage's routing system. You will see below how routes are defined programmatically, but before diving into code, let us explain how to configure them at app level. In spite of the fact that plugin developers choose a default route path for the routes their plugin provides, paths are configurable, so app integrators can set a custom path to a route whenever they like to (more information in the following sections). -There are 3 types of route references: regular route, sub route, and external route, and we will cover both the concept and code definition for each. Keep reading 🙂! +There are 3 types of route references: regular route, sub route, and external route, and we will cover both the concept and code definition for each. ### Creating a Route Reference Route references, also known as "absolute" or "regular" routes, are created as follows: -```tsx -// plugins/catalog/src/routes.ts +```tsx title="plugins/catalog/src/routes.ts" showLineNumbers import { createRouteRef } from '@backstage/frontend-plugin-api'; +// Creates a route reference, which is not yet associated with any plugin page export const indexRouteRef = createRouteRef(); ``` Note that you often want to create the route references themselves in a different file than the one that creates the plugin instance, for example a top-level routes.ts. This is to avoid circular imports when you use the route references from other parts of the same plugin. -### Providing Route References to Plugins - Route refs do not have any behavior themselves. They are an opaque value that represents route targets in an app, which are bound to specific paths at runtime. Their role is to provide a level of indirection to help link together different pages that otherwise wouldn't know how to route to each other. -The previous section code snippet does not indicate which plugin the route belongs to. To do so, you have to use it in the creation of any kind of routable extension, such as a page extension. When this extension is installed in the app it will become associated with the newly created `RouteRef`, making it possible to use the route ref to navigate the extension. Here's what we need to do: +### Providing Route References to Plugins -```tsx -// plugins/catalog/src/routes.ts -import { createRouteRef } from '@backstage/frontend-plugin-api'; +The previous section code snippet does not indicate which plugin the route belongs to. To do so, you have to use it in the creation of any kind of routable extension, such as a page extension: -export const indexRouteRef = createRouteRef(); // [1] - -// plugins/catalog/src/plugin.tsx +```tsx title="plugins/catalog/src/plugin.tsx" {11,17-19} showLineNumbers import React from 'react'; import { createPlugin, @@ -57,42 +49,36 @@ import { } from '@backstage/frontend-plugin-api'; import { indexRouteRef } from './routes'; -// The `name` option is omitted since this is the index page const catalogIndexPage = createPageExtension({ - // [2] + // The `name` option is omitted because this is an index page defaultPath: '/entities', routeRef: indexRouteRef, - // Promise.resolve is a replacement for `import(...)` - loader: () => Promise.resolve(
Index Page
), + loader: () => import('./components').then(m => ), }); export default createPlugin({ - // [3] id: 'catalog', routes: { index: indexRouteRef, }, extensions: [catalogIndexPage], }); - -// plugins/catalog/src/index.ts -export { default } from './plugin'; ``` -We have completed our journey of creating a plugin page route. This is what the code does: +In the example above we have associated a route ref with an plugin page. This is what the code does: -- [1] The line 1 creates a route reference, which is not yet associated with any plugin page; -- [2] We associate our route reference with our page by providing it as an option during creation of the page extension. -- [3] Finally, our plugin provides both routes and extensions. +- Line 11: Associates the `indexRouteRef` with the `catalogIndexPage` extension; +- Line 17 to 19: Provides both the `indexRouteRef` and `catalogIndexPage` via the Catalog plugin. -It may be unclear why we need to pass the route to the plugin when it has already been passed to the extension. We do that to make it possible for other plugins to route to our page, which is explained in detail in the (Binding External Route References)[#building-external-route-references] section. +When this extension is installed in the app it will become associated with the newly created `RouteRef`, making it possible to use the route ref to navigate the extension. -### Route Path Parameters +It may be unclear why we configure the routes option when creating a plugin as the route has already been passed to the extension. We do that to make it possible for other plugins to route to our page, which is explained in detail in the [binding routes](#binding-external-route-references) section. + +### Defining References with Path Parameters Route references optionally accept a `params` option, which will require the listed parameter names to be present in the route path. Here is how you create a reference for a route that requires a `kind`, `namespace` and `name` parameters, like in this path `/entities/:kind/:namespace/:name`: -```tsx -// plugins/catalog/src/routes.ts +```tsx title="plugins/catalog/src/routes.ts" {5} showLineNumbers import { createRouteRef } from '@backstage/frontend-plugin-api'; export const detailsRouteRef = createRouteRef({ @@ -105,303 +91,134 @@ export const detailsRouteRef = createRouteRef({ Route references can be used to link to page in the same plugin, or to pages in a different plugins. In this section we will cover the first scenario, if you are interested in link to a page of a different plugin, please go to the [external routes](#external-router-references) section below. -Let's presume that we have a plugin that renders two different pages, and these pages link to each other. +Suppose we are creating a plugin that renders a Catalog index page with a link to a "Foo" component details page. Here is the code for the index page: -First lets create the routes references: - -```tsx -// plugins/catalog/src/routes.ts -import { createRouteRef } from '@backstage/frontend-plugin-api'; - -export const indexRouteRef = createRouteRef(); - -export const detailsRouteRef = createRouteRef({ - // The parameters that must be included in the path of this route reference - params: ['kind', 'namespace', 'name'], -}); -``` - -Now we are ready to provide these routes via plugin extensions and link between pages: - -```tsx -// plugins/catalog/src/plugin.tsx +```tsx title="plugins/catalog/src/components/IndexPage.tsx" {6,11-15} showLineNumbers import React from 'react'; -import { useParams } from 'react-router'; -import { - createPlugin, - createPageExtension, - useRouteRef, -} from '@backstage/frontend-plugin-api'; -import { rootRouteRef, detailsRouteRef } from './routes'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { detailsRouteRef } from '../routes'; -const catalogIndexPage = createPageExtension({ - defaultPath: '/entities', - routeRef: indexRouteRef, - loader: () => { - const Component = () => { - const detailsLink = useRouteRef(detailsRouteRef); - const detailsPath = detailsLink({ - kind: 'component', - namespace: 'default', - name: 'foo', - }); - - return ( -
-

Index Page

- See details -
- ); - }; - - return Promise.resolve(); - }, -}); - -const catalogDetailsPage = createPageExtension({ - name: 'details', - // It is important to mention here if an integrator configures a different path for this page via config file - // It will be their responsibility to make sure that it contains the same parameters, although they don't necessarily need to be in the same order. - defaultPath: '/entities/:namespace/:kind/:name', - routeRef: detailsRouteRef, - loader: () => { - const Component = () => { - // Getting the parameters from the URL - const params = useParams(); - return ( -
-

Details Page

-
    -
  • Kind: {params.kind}
  • -
  • Namespace: {params.namespace}
  • -
  • Name: {params.name}
  • -
-
- ); - }; - - return Promise.resolve(); - }, -}); - -export default createPlugin({ - id: 'catalog', - routes: { - index: indexRouteRef, - details: detailsRouteRef, - }, - extensions: [catalogIndexPage, catalogDetailsPage], -}); - -// plugins/catalog/src/index.ts -export { default } from './plugin'; +export const IndexPage = () => { + const getDetailsPath = useRouteRef(detailsRouteRef); + return ( +
+

Index Page

+ + See "Foo" details + +
+ ); +}; ``` -During runtime, in the index page, we used a hook `useRouteRef` to get the path to the details page. Because we are linking to pages of the same plugin, we are currently accessing the reference directly, but in the following sections, you will see how to link to pages of different plugins. +Line 6 uses a hook called `useRouteRef` to access a getter function that returns the details page path. On line 11, we call the getter, passing it an object with the kind, namespace, and name. These parameters are used to construct a concrete path to details the "Foo" details page. + +Let's see how the details page can get the parameters from the URL: + +```tsx title="plugins/catalog/src/components/DetailsPage.tsx" {6,11-13} showLineNumbers +import React from 'react'; +import { useRouteRefParams } from '@backstage/frontend-plugin-api'; +import { detailsRouteRef } from '../routes'; + +export const DetailsPage = () => { + const params = useRouteRefParams(detailsRouteRef); + return ( +
+

Details Page

+
    +
  • Kind: {params.kind}
  • +
  • Namespace: {params.namespace}
  • +
  • Name: {params.name}
  • +
+
+ ); +}; +``` + +On line 6, we are using the `useRouteRefParams` hook to retrieve the entity-composed id from the URL. The parameter object contains three values: kind, namespace, and name. We can display these values or call an API using them. + +Since we are linking to pages of the same package, we are using a route ref directly. However, in the following sections, you will see how to link to pages of different plugins. ## External Router References -Now let's assume that we want to link from the Catalog entities list page to the Scaffolder create component page. We don't want to reference the Scaffolder plugin directly, since that would create an unnecessary dependency. It would also provided little flexibility in allowing the app to tie plugins together, with the links instead being dictated by the plugins themselves. To solve this, we use an `ExternalRouteRef`. Much like regular route references, they can be passed to `useRouteRef` to create concrete URLs, but they can not be used in page extensions and instead have to be associated with a target route using route bindings in the app. +External routes are made for linking to a page of an external plugin. +For this section example, let's assume that we want to link from the Catalog entities list page to the Scaffolder create component page. + +We don't want to reference the Scaffolder plugin directly, since that would create an unnecessary dependency. It would also provided little flexibility in allowing the app to tie plugins together, with the links instead being dictated by the plugins themselves. To solve this, we use an `ExternalRouteRef`. Much like regular route references, they can be passed to `useRouteRef` to create concrete URLs, but they can not be used in page extensions and instead have to be associated with a target route using route bindings in the app. We create a new `RouteRef` inside the Scaffolder plugin, using a neutral name that describes its role in the plugin rather than a specific plugin page that it might be linking to, allowing the app to decide the final target. If the Catalog entity list page for example wants to link the Scaffolder create component page in the header, it might declare an `ExternalRouteRef` similar to this: -_Catalog Plugin_ +```tsx title="plugins/catalog/src/routes.ts" showLineNumbers +import { createExternalRouteRef } from '@backstage/frontend-plugin-api'; -```tsx -// plugins/catalog/src/routes.ts -import { - createRouteRef, - createExternalRouteRef, -} from '@backstage/frontend-plugin-api'; +export const createComponentExternalRouteRef = createExternalRouteRef(); +``` -export const indexRouteRef = createRouteRef(); -export const detailsRouteRef = createRouteRef({ - // The parameters that must be included in the path of this route reference - params: ['kind', 'namespace', 'name'], -}); -export const createComponentRouteRef = createExternalRouteRef(); +External routes are also used in a similar way as regular routes: -// plugins/catalog/src/plugin.tsx +```tsx title="plugins/catalog/src/components/IndexPage.tsx" {6,10} showLineNumbers +import React from 'react'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { createComponentExternalRouteRef } from '../routes'; + +export const IndexPage = () => { + const getCreateComponentPath = useRouteRef(createComponentExternalRouteRef); + return ( +
+

Index Page

+ Create Component +
+ ); +}; +``` + +Given the above binding, using `useRouteRef(createComponentExternalRouteRef)` within the Catalog plugin will let us create a link to whatever path the Scaffolder create component page is mounted at. Note that there is no direct dependency between the Catalog plugin and Scaffolder, that is, we are not importing the `createComponentExternalRouteRef` from the Scaffolder package. + +Now the only thing left is to provide the page and external route via a plugin: + +```tsx title="plugins/catalog/src/plugin.tsx" {20-23} showLineNumbers import React from 'react'; import { createPlugin, createPageExtension, useRouteRef, } from '@backstage/frontend-plugin-api'; -import { - indexRouteRef, - detailsRouteRef, - createComponentRouteRef, -} from './routes'; +import { indexRouteRef, createComponentExternalRouteRef } from './routes'; const catalogIndexPage = createPageExtension({ defaultPath: '/entities', routeRef: indexRouteRef, - loader: () => { - const Component = () => { - const createComponentLink = useRouteRef(createComponentRouteRef); - - return ( -
-

Index Page

- {/* Linking to a create component page without direct reference */} - Create Component -
- ); - }; - - return Promise.resolve(); - }, -}); - -const catalogDetailsPage = createPageExtension({ - name: 'details', - defaultPath: '/entities/:namespace/:kind/:name', - routeRef: detailsRouteRef, - loader: () => { - const Component = () => { - // Getting the parameters from the URL - const params = useParams(); - return ( -
-

Details Page

-
    -
  • Kind: {params.kind}
  • -
  • Namespace: {params.namespace}
  • -
  • Name: {params.name}
  • -
-
- ); - }; - - return Promise.resolve(); - }, + loader: () => import('./components').then(m => ), }); export default createPlugin({ id: 'catalog', routes: { index: indexRouteRef, - details: detailsRouteRef, }, externalRoutes: { - createComponent: createComponentRouteRef, + createComponent: createComponentExternalRouteRef, }, - extensions: [catalogIndexPage, catalogDetailsPage], + extensions: [catalogIndexPage], }); - -// plugins/catalog/src/index.ts -export { default } from './plugin'; ``` -_Scaffolder Plugin_ +External routes can also have parameters. For example, if you want to link to an entity's details page from Scaffolder, you'll need to create an external route that receives the same parameters the Catalog details page expects: -```tsx -// plugins/scaffolder/src/routes.ts -import { createRouteRef } from '@backstage/frontend-plugin-api'; +```tsx title="plugins/scaffolder/src/routes.ts" {4} showLineNumbers +import { createExternalRouteRef } from '@backstage/frontend-plugin-api'; -export const indexRouteRef = createRouteRef(); - -// plugins/scaffolder/src/plugin.tsx -import React from 'react'; -import { - createPlugin, - createPageExtension, -} from '@backstage/frontend-plugin-api'; -import { indexRouteRef } from './routes'; - -const scaffolderIndexPage = createPageExtension({ - defaultPath: '/create', - routeRef: indexRouteRef, - loader: () => - Promise.resolve( -
-

Create Component

-
, - ), -}); - -export default createPlugin({ - id: 'scaffolder', - routes: { - index: indexRouteRef, - }, - extensions: [scaffolderIndexPage], -}); - -// plugins/scaffolder/src/index.ts -export { default } from './plugin'; -``` - -The Scaffolder plugin can also expose an external route that redirects to the Catalog entity details page whenever a new component is created, such as: - -```ts -// plugins/scaffolder/src/routes.ts -import { - createRouteRef, - createExternalRouteRef, -} from '@backstage/frontend-plugin-api'; - -export const indexRouteRef = createRouteRef(); -export const componentDetailsExternalRouteRef = createExternalRouteRef({ - // The parameters that must be included in the path of this route reference +export const entityDetailsExternalRouteRef = createExternalRouteRef({ params: ['kind', 'namespace', 'name'], }); - -// plugins/scaffolder/src/plugin.tsx -import React from 'react'; -import { - createPlugin, - createPageExtension, - useRouteRef, -} from '@backstage/frontend-plugin-api'; -import { indexRouteRef, componentDetailsExternalRouteRef } from './routes'; - -const scaffolderIndexPage = createPageExtension({ - defaultPath: '/create', - routeRef: indexRouteRef, - loader: () => { - const Component = () => { - const componentDetailsLink = useRouteRef( - componentDetailsExternalRouteRef, - ); - - return ( -
-

Create Component

- - See component details - -
- ); - }; - - return Promise.resolve(); - }, -}); - -export default createPlugin({ - id: 'scaffolder', - routes: { - index: indexRouteRef, - }, - externalRoutes: { - componentDetails: componentDetailsExternalRouteRef, - }, - extensions: [scaffolderIndexPage], -}); - -// plugins/scaffolder/src/index.ts -export { default } from './plugin'; ``` -Note that external routes can also have path parameters! Now let's move on and configure the app to resolve these external routes, so that the Scaffolder links to the Catalog entity page, and the Catalog links to the Scaffolder page. ### Binding External Route References @@ -410,8 +227,7 @@ The association of external routes is controlled by the app. Each `ExternalRoute Using the above example of the Catalog entities list page to the Scaffolder create component page, we might do something like this in the app configuration file: -```yaml -# app-config.yaml +```yaml title="app-config.yaml" {5,7} showLineNumbers app: routes: bindings: @@ -423,8 +239,7 @@ app: We also have the ability to express this in code as an option to `createApp`, but you of course only need to use one of these two methods: -```tsx -// packages/app/src/App.tsx +```tsx title="packages/app/src/App.tsx" {6-13} showLineNumbers import { createApp } from '@backstage/frontend-app-api'; import catalog from '@backstage/plugin-catalog'; import scaffolder from '@backstage/plugin-scaffolder'; @@ -443,70 +258,41 @@ const app = createApp({ export default app.createRoot(); ``` -Given the above binding, using `useRouteRef(createComponentRouteRef)` within the Catalog plugin will let us create a link to whatever path the Scaffolder create component page is mounted at. - Note that we are not importing and using the `RouteRef`s directly in the app, and instead rely on the plugin instance to access routes of the plugins. This provides better namespacing and discoverability of routes, as well as reduce the number of separate exports from each plugin package. Another thing to note is that this indirection in the routing is particularly useful for open source plugins that need to leave flexibility in how they are integrated. For plugins that you build internally for your own Backstage application, you can choose to go the route of direct imports or even use concrete routes directly. Although there can be some benefits to using the full routing system even in internal plugins. It can help you structure your routes, and as you will see further down it also helps you manage route parameters. ### Optional External Route References -It is possible to define an `ExternalRouteRef` as optional, so it is not required to bind it in the app. When calling `useRouteRef` with an optional external route, its return signature is changed to `RouteFunc | undefined`, and the returned value can be used to decide whether a certain link should be displayed or if an action should be taken: +It is possible to define an `ExternalRouteRef` as optional, so it is not required to bind it in the app. -```tsx -// plugins/catalog/src/routes.ts -import { - createRouteRef, - createExternalRouteRef, -} from '@backstage/frontend-plugin-api'; +```tsx title="plugins/catalog/src/routes.ts" {4} showLineNumbers +import { createExternalRouteRef } from '@backstage/frontend-plugin-api'; -export const indexRouteRef = createRouteRef(); -export const createComponentRouteRef = createExternalRouteRef({ +export const createComponentExternalRouteRef = createExternalRouteRef({ optional: true, }); +``` -// plugins/catalog/src/plugin.tsx +When calling `useRouteRef` with an optional external route, its return signature is changed to `RouteFunc | undefined`, and the returned value can be used to decide whether a certain link should be displayed or if an action should be taken: + +```tsx title="plugins/catalog/src/components/IndexPage.tsx" {11} showLineNumbers import React from 'react'; -import { - createPlugin, - createPageExtension, - useRouteRef, -} from '@backstage/frontend-plugin-api'; -import { indexRouteRef, createComponentRouteRef } from './routes'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { createComponentExternalRouteRef } from '../routes'; -const catalogIndexPage = createPageExtension({ - defaultPath: '/entities', - routeRef: indexRouteRef, - loader: () => { - const Component = () => { - const createComponentLink = useRouteRef(createComponentRouteRef); - - return ( -
-

Index Page

- {/* Since the route is optional, rendering the link only if the href is defined */} - {link && Create Component} -
- ); - }; - - return Promise.resolve(); - }, -}); - -export default createPlugin({ - id: 'catalog', - routes: { - index: indexRouteRef, - }, - externalRoutes: { - createComponent: createComponentRouteRef, - }, - extensions: [catalogIndexPage], -}); - -// index.ts -export { default } from './plugin'; +export const IndexPage = () => { + const getCreateComponentPath = useRouteRef(createComponentExternalRouteRef); + return ( +
+

Index Page

+ {/* Rendering the link only if the getCreateComponentPath is defined */} + {getCreateComponentPath && ( + Create Component + )} +
+ ); +}; ``` ## Sub Route References @@ -515,8 +301,7 @@ The last kind of route refs that can be created are `SubRouteRef`s, which can be For example: -```tsx -// plugins/catalog/src/routes.ts +```tsx title ="plugins/catalog/src/routes.ts" {4,7} showLineNumbers import { createRouteRef, createSubRouteRef, @@ -527,91 +312,65 @@ export const detailsSubRouteRef = createSubRouteRef({ parent: indexRouteRef, path: '/details', }); - -// plugins/catalog/src/plugin.ts -import React from 'react'; -import { Routes, Route, useLocation } from 'react-router-dom'; -import { - createPlugin, - createPageExtension, - useRouteRef, -} from '@backstage/frontend-plugin-api'; -import { indexRouteRef, detailsSubRouteRef } from './routes.ts'; - -const DetailsPage = () => ( -
-

Details Sub Page

-
-); - -const catalogIndexPage = createPageExtension({ - defaultPath: '/entities', - routeRef: indexRouteRef, - loader: () => { - const Component = () => { - const { pathname } = useLocation(); - const indexLink = useRouteRef(indexRouteRef); - const detailsLink = useRouteRef(detailsSubRouteRef); - - return ( -
-

Index Page

- {/* Linking to the index route */} - {pathname === detailsLink() && Hide details} - {/* Linking to the details sub route */} - {pathname === indexLink() && Show details} - {/* Registering the details sub route */} - - } /> - -
- ); - }; - - return Promise.resolve(); - }, -}); - -export default createPlugin({ - id: 'catalog', - routes: { - index: indexRouteRef, - details: detailsSubRouteRef, - }, - extensions: [catalogIndexPage], -}); - -// plugins/catalog/src/index.ts -export { default } from './plugin'; ``` -Subroutes can also receive parameters, here is an example: +There are substantial differences between creating subroutes and regular or external routes because subroutes are associated with regular routes, and the sub route path must be specified. The path string must include the parameters if this sub route has them: -```tsx -// plugins/catalog/src/routes.ts -import { - createRouteRef, - createSubRouteRef, -} from '@backstage/frontend-plugin-api'; - -export const indexRouteRef = createRouteRef(); +```tsx title ="plugins/catalog/src/routes.ts" {4} showLineNumbers +// Omitting rest of the previous example file export const detailsSubRouteRef = createSubRouteRef({ parent: indexRouteRef, path: '/:name/:namespace/:kind', }); +``` -// plugins/catalog/src/plugin.ts +Using subroutes in a page extension is as simple as this: + +```tsx title="plugins/catalog/src/components/IndexPage.ts" showLineNumbers import React from 'react'; -import { Routes, Route, Link, useParams, useLocation } from 'react-router-dom'; -import { - createPlugin, - createPageExtension, - useRouteRef, -} from '@backstage/frontend-plugin-api'; -import { indexRouteRef, detailsSubRouteRef } from './routes.ts'; +import { Routes, Route, useLocation } from 'react-router-dom'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { indexRouteRef, detailsSubRouteRef } from '../routes'; +import { DetailsPage } from './DetailsPage'; -const DetailsPage = () => { - // Getting the parameters from the URL +export const IndexPage = () => { + const { pathname } = useLocation(); + const getIndexPath = useRouteRef(indexRouteRef); + const getDetailsPath = useRouteRef(detailsSubRouteRef); + return ( +
+

Index Page

+ {/* Linking to the details sub route */} + {pathname === getIndexPath() ? ( + // Setting the details sub route params + + Show details + + ) : ( + Hide details + )} + {/* Registering the details sub route */} + + } /> + +
+ ); +}; +``` + +This is how you can get the parameters of a sub route URL: + +```tsx title="plugins/catalog/src/components/DetailsPage.ts" {5} showLineNumbers +import React from 'react'; +import { useParams } from 'react-router-dom'; + +export const DetailsPage = () => { const params = useParams(); return (
@@ -624,60 +383,22 @@ const DetailsPage = () => {
); }; +``` + +Finally, see how a plugin can provide subroutes: + +```tsx title="plugins/catalog/src/plugin.ts" {18} showLineNumbers +import React from 'react'; +import { + createPlugin, + createPageExtension, +} from '@backstage/frontend-plugin-api'; +import { indexRouteRef, detailsSubRouteRef } from './routes'; const catalogIndexPage = createPageExtension({ defaultPath: '/entities', routeRef: indexRouteRef, - loader: () => { - const Component = () => { - const { pathname } = useLocation(); - const indexLink = useRouteRef(indexRouteRef)(); - const detailsLink = useRouteRef(detailsSubRouteRef); - - const indexPath = indexLink(); - const detailsPath = detailsLink({ - // Setting the details subroute params - kind: 'component', - namespace: 'default', - name: 'foo', - }); - - return ( -
-

Index Page

- - - - - - - - - - - - - -
NameDetails
Foo - {/* Linking to the index route */} - {pathname === detailsPath && ( - Hide details - )} - {/* Linking to the details sub route */} - {pathname === indexPath && ( - Show details - )} -
- {/* Registering the details subroute */} - - } /> - -
- ); - }; - - return Promise.resolve(); - }, + loader: () => import('./components').then(m => ), }); export default createPlugin({ @@ -688,7 +409,4 @@ export default createPlugin({ }, extensions: [catalogIndexPage], }); - -// plugins/catalog/src/index.ts -export { default } from './plugin'; ```