Skip to main content

Navigation Nodes and Routing

Left Navigation Node#

Reach plugins can hook into the left navigation sidebar. See the "Tasks" node in the screenshot below:

Configuration#

Left navigation nodes are configured in an extension with an area of type Page. This can be configured in the definition.json file. The route property specifies the path segment that will be used to serve the provided React component. In the example below the TaskRoutes component will be rendered under the path https://reach.livetiles.io/<handle>/tasks. The navigationNode property defines how the node should look in the left navigation.

{
"pluginId": "tasks-plugin",
"extensions": [
{
"area": "Page",
"componentName": "TaskRoutes",
"route": "tasks", // <== defines the URL path segment
"navigationNode": {
// <== describes the node
"label": "Tasks",
"translations": {
"de": "Aufgaben"
},
"iconName": "TaskLogo"
}
}
]
}

The navigationNode property is used to describe the left navigation entry:

PropertyTypeDescription
labelstringDefines the label displayed next to the icon in the navigation node.
translations (optional)objectThis key value pair is used to provide translations. Each key represent a language country code (like "en") while the value represents that language's translation of the navigation node (like "Tasks"). If a language cannot be found it will automatically fallback to the value of the label.
iconNamestringDefines the friendly name of a Fluent UI icon (formerly office ui fabric). Browse Icons

Custom Navigation Node#

You may want to conditionally render the navigation node or even render a custom component in the left navigation bar. In order to have a custom rendered component for your plugin's navigation node, we have to make a small change in the definition.json file. For each navigation node you may specify a componentName which will be rendered instead of the default NavigationNode from Reach. As an example we will modify the tasks-plugin to render our own navigation node:

{
"pluginId": "tasks-plugin",
"extensions": [
{
"area": "Page",
"componentName": "TaskRoutes",
"route": "tasks",
"navigationNode": {
"componentName": "TaskNavigationNode", // <-- add this line
"label": "Tasks",
"translations": {
"de": "Aufgaben"
},
"iconName": "TaskLogo"
}
}
]
}

Removing the other properties from the navigationNode is not required as anything you have here will be passed down through a specific property. The next step is to create a component with the same name as we specified for the componentName under navigationNode. This component will behave the same way as any other component used for your plugin. That means you will need to export it in your index.ts file so it can be picked up by Reach. By setting this component as a custom navigation node, we get access to a special property named navigationNodeProps. This property will contain all the neccesary properties required to render the default Navigation Node from Reach.

Let's take a look at an example, where we want to conditionally hide the navigation node:

import { FC } from 'react';
import { usePluginSettings, PluginNavigationNodeProps } from '@reach/core';
import { PageMenuActionItem } from '@reach/chrome';
export const TaskNavigationNode: FC<PluginNavigationNodeProps> = ({
navigationNodeProps,
}) => {
const { showNavNode } = usePluginSettings<{ showNavNode?: string }>();
if (!showNavNode) {
return null;
}
return <PageMenuActionItem {...navigationNodeProps} />;
};

Here we read the value of showNavNode from the plugin settings (for more information see: settings) and depending on it's value we either render the node or not. In the example above we decided to render the default Navigation Node from Reach. For this we use the component named PageMenuActionItem which is exported from @reach/chrome. We have to pass in the navigationNodeProps so the component knows how to render our navigation node.

Routing#

Reach plugins can include sub-pages as part of the Page extension areas.

These sub menu nodes are defined via the Page component exported from @reach/chrome. The Page component accepts an optional property submenuContents. Sub nodes can be rendered with NavLinkSidebarItem components:

import { FC } from 'react';
import { useRouteMatch } from 'react-router-dom';
import { Page } from '@reach/chrome';
const PendingTasksPage: FC = () => {
return (
<Page title="Pending Tasks" submenuContents={<Menu />}>
<Host>
<Container>
<Title>Open Tasks</Title>
<SubTitle>All the work I still have to do</SubTitle>
</Container>
</Host>
</Page>
);
};
// ...
const Menu: FC = () => {
// find the subscription handle and the plugin route
// to construct the proper navigation path.
const match = useRouteMatch();
const handle = match.path.split('/')[1];
const pluginRoute = match.path.split('/')[2];
return (
<div>
<NavLinkSidebarItem
route={`/${handle}/${pluginRoute}/pending`}
title="Pending Tasks"
iconName="BulletedList2"
/>
<NavLinkSidebarItem
route={`/${handle}/${pluginRoute}/completed`}
title="Completed Tasks"
iconName="Accept"
/>
</div>
);
};

After we've defined the left navigation and its target routes, we now need to set up the actual routing, to match URLs with page components like the PendingTasksPage from the example above.

This is done via a react-router configuration. Reach already bundles an instance of react-router and react-router-dom which can be used in plugins. In order to setup routing we need to define routes via the Route component and access the current URL via the useRouteMatch hook.

import { FC } from 'react';
import { Switch, Route, Redirect, useRouteMatch } from 'react-router-dom';
import { PluginProps } from '@reach/core';
export const TaskRoutes: FC<PluginProps> = () => {
const match = useRouteMatch();
return (
<Switch>
<Route path={match.url + '/pending'} component={PendingTasksPage} />
<Route path={match.url + '/completed'} component={CompletedTasksPage} />
<Redirect path={match.url} exact={true} to={match.url + '/pending'} />
<Route component={NotFoundPage} />
</Switch>
);
};

Please refer to the React Router documentation to read more about how to setup the routing.

You can check out the full example on GitHub.