Routing
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;