How we use react-router
The implementation of routes and navigation in MilMove using react-router
has led to the development of several common code and testing patterns. This document's purpose is to provide an overview and examples of the most frequently employed techniques and patterns within MilMove. For more on react-router
functionality, refer to the latest react-router documentation.
Navigate
The primary method for redirecting to a new URL using react-router
is through the Navigate
function. This function serves as a replacement for history.push()
. It can be invoked as a function or utilized as a component-based version in class components where hooks are not available. One important consideration with Navigate
is that if the path does not start with a leading slash, it is resolved relative to the parent route.
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
// Root path (starts with '/')
navigate('/absolute/path/to/page');
// No leading slash, path is relative
navigate('relative/path'); // go to current URL + relative/path
// Go back
navigate(-1);
Links
Links (and NavLinks) are also commonly used in MilMove to navigate between pages. Similar to Navigate
, links can employ relative routing when the path lacks a leading slash. The react-router documentation describes this well:
"A relative
<Link to>
value (that does not begin with/
) resolves relative to the parent route, which means that it builds upon the URL path that was matched by the route that rendered that<Link>
. It may contain..
to link to routes further up the hierarchy. In these cases,..
works exactly like the command-linecd
function; each..
removes one segment of the parent path."
import { Link } from 'react-router-dom';
// Root path (starts with '/')
<Link to="/absolute/path/to/page" />
// relative path
<Link to="relative/path" />
// remove one segmet of parent path, then relative path
<Link to="../relative/path" relative="path"/>
URL Parameters (Params)
URL parameters can be accessed using the useParams()
hook.
import { useParams } from 'react-router-dom';
// On a route with path something like: /moves/:moveCode/details
const { moveCode } = useParams();
Routes
In react-router
version 6, the <Switch>
component was replaced with the <Routes>
component. The most significant consequence of this change is that all children of the <Routes>
component must be pure react-router
<Route>
components. Previously, we employed customized route components such as <PrivateRoute>
, which handled tasks like permission checks and displaying 'Forbidden' pages. This functionality has been refactored, and we now exclusively use pure <Route>
components. For more details, refer to the informative guide on composing <Route>
in react-router v6.
Testing and Storybook
When rendering components that depend on react-router
functionality, an error may occur (... may be used only in the context of a <Router> component
) due to the absence of a react-router
setup, as would be present in the application. This scenario is quite common and we have testing utilities to aid in setting up react-router
(and other providers) for isolated component testing or Storybook.
These utility functions and components are locate in src/testUtils.jsx.
Routing Test Utilities
If you only require a mocked react-router
, you can employ the renderWithRouter
function, the MockRouterProvider
component, or the renderWithRouterProp
function for class components that use a router
prop. See Additional Providers below for including additional providers.
Props and Function Arguments
The props and arguments for these test utilities have undergone slight changes from our previous (similar) testing utilities. All props are optional and come with reasonable defaults. Therefore, provide them only when specific routing is necessary for the component. The primary props required for customizing routing are path
and params
. The path
represents a react-router
string path, which should typically be sourced from constants/routes.js. The params
correspond to the values for dynamic segments within the path
(segments starting with a colon). These two values ensure the proper execution of routing functionality and hooks like useParams
without requiring individual mocks, as was previously needed with our testing utilities.
renderWithRouter
This function is used to render a component with React Testing Library (RTL) and set up react-router
.
import { renderWithRouter } from 'testUtils';
const routingConfig = {
path: customerRoutes.SHIPMENT_CREATE_PATH, // /moves/:moveId/new-shipment
params: { moveId: '976d1b02-afee-4e6e-9fe4-a18ced45807e' },
};
renderWithRouter(<MtoShipmentForm />, routingConfig);
MockRouterProvider
Used when you need a routing provider setup but do not want to render with RTL. Common uses include storybook and enzyme testing.
import { MockRouterProvider } from 'testUtils';
// Enzyme mount the QAE Report Header componenr with react router setup
const wrapper = mount(
<MockRouterProvider
path={qaeCSRRoutes.BASE_EVALUATION_REPORT_PATH}
params={{ moveCode: 'AMDORD', reportId: 'abc123' }}
>
<QaeReportHeader />
</MockRouterProvider>,
);
Additional Providers
In addition to react-router, there are three other providers frequently required for rendering components in isolation: react-query
, permissions
, and redux
. Similar to the routing provider utilities, we offer utility functions and components that set up and include all these providers. The most commonly used utilities are renderWithProviders
and MockProviders
, which mirror the use cases of the routing-only utilities but include all other providers as well.