ยท react

Upgrade to React Router v6

React Router Version 6 is great for TypeScript programmers because it comes with type definitions. It also introduces the `useRoutes` hook, which simplifies routing setups in functional React components. Additionally, the new `Outlet` API allows for rendering child components. The article provides examples of how routing was done in React Router v5 and how it has changed in v6.

React Router Version 6 is great for TypeScript programmers because it ships with type definitions. Another great feature is the useRoutes hook, which simplifies routing setups in your functional React components. You can also render child components by using the new Outlet API.

Contents

Before React Router v6

Prior to React Router v6, you had to install external type definitions along with the React Router packages:

This is an example of how routing with React Router v5 was done:

Router component

index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
 
import App from './App';
 
const element = (
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
 
const container = document.getElementById('root');
 
ReactDOM.render(element, container);

Route configuration

Route setup separated by layouts. Routes are evaluated in order. The Switch component guarantees that there are only exclusive matches. No other Route definition will be rendered if there is a match. If there is no match, the wildcard route of * will be used to render a page not found view:

App.tsx
import React from 'react';
import {Route, Switch} from 'react-router';
 
import AccountLayout from './account/AccountLayout';
import MainLayout from './main/MainLayout';
import PageNotFoundView from './error/PageNotFoundView';
 
const App: React.FC = (): JSX.Element => {
  return (
    <>
      <Switch>
        <Route exact path='/' component={MainLayout} />
        <Route path='/account' component={AccountLayout} />
        <Route path='*'>
          <PageNotFoundView />
        </Route>
      </Switch>
    </>
  );
};
 
export default App;

The main layout shows how to link to different views by using the Link component:

main/MainLayout.tsx
import React from 'react';
import {Route} from 'react-router';
import {Link} from 'react-router-dom';
 
import MainView from './MainView';
 
const MainLayout: React.FC = (): JSX.Element => {
  return (
    <>
      <nav>
        <ul>
          <li><Link to='/'>Main Page</Link></li>
          <li><Link to='/account/add'>Add Account</Link></li>
          <li><Link to='/account/list'>List Accounts</Link></li>
          <li><Link to='/account/1'>View Account</Link></li>
          <li><Link to='/something-else'>Not Found</Link></li>
        </ul>
      </nav>
      <Route exact path='/' component={MainView} />
    </>
  );
};
 
export default MainLayout;

The account layout defines child routes of the application with their respective components. Pay attention to the order of the routes: /account/:id comes last because the :id parameter will match anything after /account and would also match /account/add and others.

account/AccountLayout.tsx
import React from 'react';
import {Redirect} from 'react-router';
import {Link, Route, Switch} from 'react-router-dom';
 
import AccountAddView from './AccountAddView';
import AccountDetailView from './AccountDetailView';
import AccountListView from './AccountListView';
 
const AccountLayout: React.FC = (): JSX.Element => {
  return (
    <div style={{backgroundColor: 'yellow'}}>
      <Switch>
        <Route exact path='/account/add' component={AccountAddView} />
        <Route exact path='/account/list' component={AccountListView} />
        <Route exact path='/account/:id' component={AccountDetailView} />
        <Redirect exact from='/account' to='/account/list' />
      </Switch>
      <br />
      <button>
        <Link to='/'>Back</Link>
      </button>
    </div>
  );
};
 
export default AccountLayout;

Props from routes

If you want to use matching parameters from your routes, you will have to define RouteComponentProps in your React component:

account/AccountDetailView.tsx
import React from 'react';
import {RouteComponentProps} from 'react-router-dom';
 
interface MatchParams {
  id: string
}
 
interface Props extends RouteComponentProps<MatchParams> {
 // ...
}
 
const AccountDetailView: React.FC<Props> = ({match}: Props): JSX.Element => {
  const params = match.params;
  return <>{`View Account ID "${params.id}"`}</>;
};
 
export default AccountDetailView;

React Router v6

Installation

You don't need to install additional typings with React Router v6. You will be good by adding just these two packages to your web application:

Router

You will have to wrap your main app component in a Router component:

index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
 
import App from './App';
 
const element = (
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
 
const container = document.getElementById('root');
 
ReactDOM.render(element, container);

If you don't follow this rule, you are likely to run into the following error:

Uncaught Error: useRoutes() may be used only in the context of a component.

Nested Routing

When your app is wrapped in a <Router> component, you can define its routes. React Router v6 provides a useRoutes hook to do that:

App.tsx
import React from 'react';
import {Navigate, useRoutes} from 'react-router-dom';
 
import AccountAddView from './account/AccountAddView';
import AccountDetailView from './account/AccountDetailView';
import AccountLayout from './account/AccountLayout';
import AccountListView from './account/AccountListView';
 
import MainLayout from './main/MainLayout';
import MainView from './main/MainView';
 
import PageNotFoundView from './error/PageNotFoundView';
 
const App: React.FC = (): JSX.Element => {
  const mainRoutes = {
    path: '/',
    element: <MainLayout />,
    children: [
      {path: '*', element: <Navigate to='/404' />},
      {path: '/', element: <MainView />},
      {path: '404', element: <PageNotFoundView />},
      {path: 'account', element: <Navigate to='/account/list' />},
    ],
  };
 
  const accountRoutes = {
    path: 'account',
    element: <AccountLayout />,
    children: [
      {path: '*', element: <Navigate to='/404' />},
      {path: ':id', element: <AccountDetailView />},
      {path: 'add', element: <AccountAddView />},
      {path: 'list', element: <AccountListView />},
    ],
  };
 
  const routing = useRoutes([mainRoutes, accountRoutes]);
 
  return <>{routing}</>;
};
 
export default App;

By looking at the code above, you may have noticed that React Router supports nested routing where you can define routes for different parts of your application with different layouts. This is possible because of the <Outlet /> component, which is a placeholder for the elements that should be rendered on the child routes / paths.

Here is the code of the AccountLayout component to showcase the new Outlet API:

account/AccountLayout.tsx
import React from 'react';
import {Link, Outlet} from 'react-router-dom';
 
const AccountLayout: React.FC = (): JSX.Element => {
  return (
    <div style={{backgroundColor: 'yellow'}}>
      <Outlet />
      <br />
      <button>
        <Link to='/'>Back</Link>
      </button>
    </div>
  );
};
 
export default AccountLayout;

Navigation between different views is done with the Link component, which uses navigate under the hood and is the preferred way to make URL navigations. The use of history and useHistory is deprecated and should be replaced with the useNavigate hook. The React Router team provides a Migration Guide in this regard.

main/MainLayout.tsx
import React from 'react';
import {Link, Outlet} from 'react-router-dom';
 
const MainLayout: React.FC = (): JSX.Element => {
  return (
    <>
      <nav>
        <ul>
          <li><Link to='/'>Main Page</Link></li>
          <li><Link to='/account/add'>Add Account</Link></li>
          <li><Link to='/account/list'>List Accounts</Link></li>
          <li><Link to='/account/1'>View Account</Link></li>
          <li><Link to='/something-else'>Not Found</Link></li>
        </ul>
      </nav>
      <Outlet />
    </>
  );
};
 
export default MainLayout;

Props and match

There is no more need to extend your component's props with the properties of match. You can retrieve parameters from your routing with the useParams hook:

account/AccountDetailView.tsx
import React from 'react';
import {useParams} from 'react-router-dom';
 
const AccountDetailView: React.FC = (): JSX.Element => {
  const params = useParams();
  return <>{`View Account ID "${params.id}"`}</>;
};
 
export default AccountDetailView;
Back to Blog