React Router

advanced

by Elizaveta Anatskaya

Different types of routers

đź“Ś BrowserRouter

/*
  You can go in history
  https://example.com/
  https://example.com/about
*/
import { BrowserRouter as Router } from 'react-router-dom';
  • The widely popular router and a router for modern browsers which user HTML5 pushState API. (i.e., pushState, replaceState and popState API).
  • It routes as normal URL in browser, you can’t differentiate whether it is server rendered page or client rendered page through the URL.
  • It assumes, your server handles all the request URL (eg., /, /about) and points to root index.html. From there, BrowserRouter take care of routing the relevant page.
  • It accepts forceRefresh props to support legacy browsers which doesn’t support HTML5 pushState API âś…

 

đź“Ś MemoryRouter

// https://example.com (same url for all routes)
import { MemoryRouter as Router } from 'react-router-dom';
  • A router which doesn’t change the URL in your browser instead it keeps the URL changes in memory
  • It is very useful for testing and non browser environments âś…
  • But in browser, It doesn’t have history. So you can’t go back or forward using browser history ❌
// you can also use a renderer like "@testing-library/react" or "enzyme/mount" here
import { render, unmountComponentAtNode } from "react-dom";
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from "react-router-dom";

// app.test.js
it("navigates home when you click the logo", async => {
  // in a real test a renderer like "@testing-library/react"
  // would take care of setting up the DOM elements
  const root = document.createElement('div');
  document.body.appendChild(root);

  // Render app
  render(
    <MemoryRouter initialEntries={['/my/initial/route']}>
      <App />
    </MemoryRouter>,
    root
  );

  // Interact with page
  act(() => {
    // Find the link (perhaps using the text content)
    const goHomeLink = document.querySelector('#nav-logo-home');
    // Click it
    goHomeLink.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });

  // Check correct page content showed up
  expect(document.body.textContent).toBe('Home');
});

đź“Ś HashRouter

/*
  Hashed routes, you can go in history.
  https://example.com/#/
  https://example.com/#/about
*/
import { HashRouter as Router } from 'react-router-dom';
  • A router which uses client side hash routing.
  • Whenever, there is a new route get rendered, it updated the browser URL with hash routes. (eg., /#/about)
  • Hash portion of the URL won’t be handled by server, server will always send the index.html for every request and ignore hash value. Hash value will be handled by react router.
  • It is used to support legacy browsers which usually doesn’t support HTML pushState API âś…
  • It doesn’t need any configuration in server to handle routes âś…
  • This route isn’t recommended by the team who created react router package. Use it only if you need to support legacy browsers or don’t have server logic to handle the client side routes ❌

đź‘ŹMeet React Routerđź‘Ź

v 5.1

 

🎣 useParams

"params" = dynamic segments of the URL

How did we use to pass params to component?

 {/* Using the `component` prop */}
<Route path="/blog/:slug" component={BlogPost} />

{/* Using the `render` prop */}
<Route
  path="/posts/:slug"
  render={({ match }) => <BlogPost match={match} />}
/>

methods only provide access to the params in <BlogPost>, so you're forced to manually pass them along to components further down the tree if you need them.

Let's see how useParams fixes all of these problems for us:

// In the code below, BlogPost is used as both 
// a <Route component> and in a <Route render> function.
// In both cases, it receives a `match`
// prop, which it uses to get the URL params.
function BlogPost({ match }) {
  let { slug } = match.params
  // ...
}

🎣 useParams

{/* No weird props here, just use
            regular `children` elements! */}
<Route path="/posts/:slug">
   <BlogPost />
</Route>
  • We don't have to do any weird component composition tricks to get the params, we can just use regular children elements
  • We can useParams() anywhere in <BlogPost> or its subtree without manually passing params around
function BlogPost() {
  // We can call useParams() here to get the params,
  // or in any child element as well!
  let { slug } = useParams()
  // ...
}

 🎣 useLocation

imagine a useEffect hook where you want to send a "page view" event to your web analytics service every time the URL changes.

import { Switch, useLocation } from 'react-router-dom'

function usePageViews() {
  let location = useLocation()

  useEffect(
    () => {
      ga.send(['pageview', location.pathname])
    },
    [location]
  )
}

function App() {
  usePageViews()
  return <Switch>{/* your routes here */}</Switch>
}

returns the current location object. This is useful any time you need to know the current URL.

🎣 useHistory

the useHistory hook is a quick stopgap for a future hook: useNavigate.

import { useHistory } from 'react-router-dom'

function BackButton({ children }) {
  let history = useHistory()
  return (
    <button type="button" onClick={() => history.goBack()}>
      {children}
    </button>
  )
}

For programmatic navigation purposes, access to the history object is provided via useHistory.

 🎣 useRouteMatch

 

 

It matches the URL exactly like a <Route> would, including the exact, strict, and sensitive options.

So instead of rendering a <Route>, just useRouteMatch instead:

 

// before
import { Route } from 'react-router-dom'

function App() {
  return (
    <div>
      {/* ... */}
      <Route
        path="/BLOG/:slug/"
        strict
        sensitive
        render={({ match }) => {
          return match ? <BlogPost match={match} /> : <NotFound />
        }}
      />
    </div>
  )
}

is useful any time you are using a <Route> just so you can get access to its match data, including all of the times you might render a <Route> all by itself outside of a <Switch>

// after
import { useRouteMatch } from 'react-router-dom'

function App() {
  let {match} = useRouteMatch({
    path: '/BLOG/:slug/',
    strict: true,
    sensitive: true
  })

  return (
    <div>
      {/* ... */}
      {match ? <BlogPost match={match} /> : <NotFound />}
    </div>
  )
}

🎉 React Router v6 🎉

Size decreased by the 70%

The 5.1 version have the size of 9.4kb but new React Route v6 gonna have size only 2.9kb.

<Switch> is becoming <Routes>

// 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>
  );
}
// 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>

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} />} />

Nested Routes are Simple

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

// 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:

// You can also define all
// <Route> in a single place
// 
// Approach #1
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>
  )
}
// v6
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  Outlet
} from 'react-router-dom';

// Approach #2
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>
  );
}

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.

// 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});

That's all for today! 

🥳

Made with Slides.com