Building an App with SPFx, React, and TypeScript Part 3: Routing

Introduction

We’re switching gears in this post and moving away from components and data to revisit our navigation from part 1. In that post, we ended up with two stateless functional components to render our navigation. One issue that you’ll have with navigation in a web part is that you can’t change the url. If you do, you’ll end up on a new SharePoint site, instead of a new page in our app.

We added a hash to our url in part 1 to prevent it from leaving the page. In the example of the members page, we use an anchor tag and our url would look like https://localhost:4321/temp/workbench.html#/members

export interface NavigationItemProps {
    url: string,
    children: React.ReactNode
}

const navigationItem = (props:NavigationItemProps) => (
    <li className={styles.NavigationItem}>
        <a 
            href={'#' + props.url}
            >{props.children}</a>
        
    </li>
);

Replacing Anchors with NavLink

We’re going to make a few changes to NavigationItem.tsx. The NavigationItemProps interface will get a new options property called exact and we’ve replaced the anchor tag with NavLink which comes from react-router-dom. The NavLink doesn’t use an href attribute. Instead, it uses “to“. You’ll notice, in the next code snippet, that we removed the hash that we were appending to the URL.

One of the benefits of NavLink is that it automatically adds an active style to the element when the url matches it. So all we need to do is add a “.active” to our css. We can also change it to something other than “active” but that’s fine for us so we’ll leave it.

import * as React from 'react';
import { NavLink } from 'react-router-dom';

import styles from '../NavigationItem/NavigationItem.module.scss';

export interface NavigationItemProps {
    url: string,
    exact?: boolean,
    children: React.ReactNode
}

const navigationItem = (props:NavigationItemProps) => (
    <li className={styles.NavigationItem}>
        <NavLink 
            to={props.url}
            exact={props.exact}
            >{props.children}</NavLink>
        
    </li> 
);

export default navigationItem;

Routing

We currently have a new “exact” property in NavigationItemProps and we replaced the anchor tag with NavLink. These changes alone will break the app. We need to wire the app up to be able to know what to do with that NavLink tag. To do that, we have to go back to the EventHub.tsx where our app begins. We need to import HashRouter from react-router-dom and wrap our app with it. In this case, we wrap Chrome with HashRouter. At this point, the app will begin loading again, our url’s work, and an active style is added to the link that matches the current url, but we’re still not done. I want the app to load components based on the url instead of seeing everything all at once.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';
import Members from '../Members/Members';
import { HashRouter } from 'react-router-dom';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <HashRouter>
        <Chrome>
          <EventInfo />
          <Members />
        </Chrome>
      </HashRouter>
    );
  }
}

I want the contents of Chrome to be dynamic based on the URL so we’re going to add a Switch to load the proper component.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';
import Members from '../Members/Members';
import { HashRouter, Switch, Route } from 'react-router-dom';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <HashRouter>
        <Chrome>
          <Switch>
              <Route path="/members" component={Members} />
              <Route path="/" component={EventInfo} exact />
              <Route render={() => <h1>Page Not found</h1>} />
          </Switch>
        </Chrome>
      </HashRouter>
    );
  }
}

There are a few things about the previous code that we need to point out. We start by importing Switch and Route from react-router-dom. We eliminated the Members and EventInfo tags from within Chrome and added Switch. Inside of Switch, we have Route tags that define what components to load when the url matches the path. Switch will allow the proper route to be selected. So if the site url contains “/members”, we load members. If the url contains “/”, our EventInfo component loads. That route also uses exact because we don’t want other url’s to load the EventInfo. In other words, without “exact” EventInfo will load whenever if finds a “/” which would include “/members” and anything else for that matter.

Now, we never created a component for the About page and I did that to show how we handled unexpected URLs. The 3rd route in our switch renders an inline function that returns an h1 tag with Page Not Found. So since we don’t have a route to define what we want to do when we visit “/about”, the Switch will trigger the 3rd route and we’ll see a file not found. I threw in a style update to our navigation to add a border-bottom when the active style is added to the appropriate link. Let’s see what all of this looks like.

In this screenshot, we can see our hashroute and switch working. Our url contains a “#/” which tells it to only load our EventInfo component. Having the hash in the url allows us to append url paths that our app can read while staying on the same SharePoint site.

Here, we can see the Members component is loaded and EventInfo is gone when the url changes to “#/members.”

Finally, we see what happens when a route that wasn’t defined in the switch is met. Here we see our page not found.

Conclusion

In this post, we introduced React components designed to allow us to introduce navigation and dynamic component loading. In the next post, we’ll revisit EventInfo, and EventHub components, and we’ll also take a first look at the web part component. We’re going to make the web part configurable and use that configuration to get data from SharePoint to populate our components.