A Sneak Peek at React Router v6

At the time of this writing, React Router v6 is still in alpha, but the time is about right to start playing with it and exploring what’s to come. This guide will give you a peek at the new features/changes!

As you may know, the lead maintainers forked the React Router project to create a lightweight alternative called Reach Router in early 2018.

During this time, both libraries grew, however it seems that active development for Reach Router will stop, and will be merged into the upcoming React Router v6 🛣

With the release coming soon, here’s a sneak peek of what’s coming!

Jump to…

<Switch> is becoming <Routes>

This top-level component is going to be renamed. However, its functionality is mostly remaining the same.

// v5
import {
  BrowserRouter,
  Switch,
  Route
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/"><Home /></Route>
        <Route path="/profile"><Profile /></Route>
      </Switch>
    </BrowserRouter>
  );
}

Just drop <Routes> in there:

// v6
import {
  BrowserRouter,
  Routes,
  Route
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile/*" element={<Profile />} />
      </Routes>
    </BrowserRouter>
  );
}

Big Changes with <Route>

In v6 the <Route> component is receiving the biggest overhaul. Fortunately, these new changes will actually be making it simpler to use!

The component/render prop will be substituted for the element prop:

import Profile from './Profile';

// v5
<Route path=":userId" component={Profile} />
<Route
  path=":userId"
  render={routeProps => (
    <Profile routeProps={routeProps} animate={true} />
  )}
/>

// v6
<Route path=":userId" element={<Profile />} />
<Route path=":userId" element={<Profile animate={true} />} />

If you noticed, in v6 it’s much easier to pass props now. This has negated the use of the render prop in v5.

Nested Routes are Simpler

Nested routes in v5 had to be very explicitly defined. This required including a lot of string-matching logic into these components. See <Profile>:

// v5
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/profile" component={Profile} />
      </Switch>
    </BrowserRouter>
  );
}

function Profile() {

  let match = useRouteMatch();

  return (
    <div>
      <nav>
        <Link to={`${match.url}/me`}>My Profile</Link>
      </nav>

      <Switch>
        <Route path={`${match.path}/me`}>
          <MyProfile />
        </Route>
        <Route path={`${match.path}/:id`}>
          <OthersProfile />
        </Route>
      </Switch>
    </div>
  );
}

In v6, you can remove the string-matching logic. There isn’t any need for useRouteMatch() either! The result is pleasantly minimal:

// v6
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  Outlet
} from 'react-router-dom';

// Approach #1
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile/*" element={<Profile/>} />
      </Routes>
    </BrowserRouter>
  );
}

function Profile() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Routes>
        <Route path="me" element={<MyProfile />} />
        <Route path=":id" element={<OthersProfile />} />
      </Routes>
    </div>
  );
}

// Approach #2
// You can also define all
// <Route> in a single place
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />}>
          <Route path=":id" element={<MyProfile />} />
          <Route path="me" element={<OthersProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Profile() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Outlet />
    </div>
  )
}

Note: the <Outlet> component is used like {this.props.children} in React Router v6. This was a very popular feature from Reach Router!

useNavigate Instead of useHistory

Sometimes you’ll want to programmatically navigate. For example, after a user submits a form and they need to be redirected to a confirmation page. This is the useHistory library in v5, which has been renamed to useNavigate in v6:

// v5
import { useHistory } from 'react-router-dom';

function MyButton() {
  let history = useHistory();
  function handleClick() {
    history.push('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

Now history.push() will be replaced with navigate():

// v6
import { useNavigate } from 'react-router-dom';

function MyButton() {
  let navigate = useNavigate();
  function handleClick() {
    navigate('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

In some cases, you’ll want to replace an URL in the browser history instead of pushing a new URL. This has slightly changed with v6:

// v5
history.push('/home');
history.replace('/home');

// v6
navigate('/home');
navigate('/home', {replace: true});

From 20kb to 8kb

With all of these changes, you’d expect the bundle size to grow, but it’s actually reduced by half! The minified bundle of v5 was ~20kb, and v6 is only ~8kb.

Screenshot: React router v5 vs v6

Bundle sizes are calculated using the BundlePhobia tool.

Conclusion

I’m pretty excited about the release of React Router v6. Hopefully this article has given you an idea of what to expect when it releases (which should be soon)! You can read more about React Router v6 in the latest release notes 📝