Skip to content

Routing

React Router logo

Routing, which is the ability to route different requested paths to different displayed pages, takes place within the browser for the single page app we are building. This is preferable to routing on the server as the user experiences faster page transitions.

Note

Client side routing does come with the cost of a larger initial download and could harm SEO (although it isn't clear to me if this is the case). It also requires a catch all backend serving route as the backend no longer knows which paths equate to pages.

React-Router is a great library to handle routing in React apps, it is installed via npm,

Run this command in frontend/

npm install --save react-router-dom
npm install --save-dev @types/react-router-dom

which installed 5.2.0.

Our apps routes can be expressed by a few exclusive paths in a Router component, frontend/src/Router.tsx,

import { BrowserRouter, Switch, Route } from "react-router-dom";

const Router = () => (
  <BrowserRouter>
    <Switch>
      // Routes to go here
      <Route>
        // Not found page here
      </Route>
    </Switch>
  </BrowserRouter>
);

export default Router;

where the Switch enforces that only the first matching Route is rendered. The final Route will then match if no others do, allowing a not found page to be shown.

This Router component is then used in the App component within the existing contexts.

Scroll to top

By default when navigating client side with this setup the scroll position will not change. This means that if the user is viewing the bottom of a page and navigates to another they'll be viewing the bottom of the new page. This is annoying, so I like to scroll the view to the top of the page on navigation using a ScrollToTop component, frontend/src/components/ScrollToTop.tsx,

import React from "react";
import { useLocation } from "react-router";

const ScrollToTop = (): null => {
  const { pathname } = useLocation();

  React.useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
};

export default ScrollToTop;

which is then placed within the BrowserRouter in the Router component.

Private Routes

A significant fraction of the routes in the app will only be available for users who are logged in. Using the Authentication context allows for a PrivateRoute component that is usable in the same why as Route components but only matches when the user is logged in. Add the following to src/components/PrivateRoute.tsx,

import React from "react";
import { Redirect, Route, RouteProps } from "react-router-dom";

import { AuthContext } from "src/AuthContext";

const PrivateRoute = ({ children, ...rest }: RouteProps) => {
  const { authenticated } = React.useContext(AuthContext);

  return (
    <Route
      {...rest}
      render={(props) => {
        if (authenticated) {
          return children;
        } else {
          return (
            <Redirect
              to={{ pathname: "/login/", state: { from: props.location } }}
            />
          );
        }
      }}
    />
  );
};

export default PrivateRoute;