advanced
by Elizaveta Anatskaya
/*
You can go in history
https://example.com/
https://example.com/about
*/
import { BrowserRouter as Router } from 'react-router-dom';
Â
// https://example.com (same url for all routes)
import { MemoryRouter as Router } from 'react-router-dom';
// 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');
});
/*
Hashed routes, you can go in history.
https://example.com/#/
https://example.com/#/about
*/
import { HashRouter as Router } from 'react-router-dom';
Â
"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
// ...
}
{/* No weird props here, just use
regular `children` elements! */}
<Route path="/posts/:slug">
<BlogPost />
</Route>
function BlogPost() {
// We can call useParams() here to get the params,
// or in any child element as well!
let { slug } = useParams()
// ...
}
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.
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.
Â
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>
)
}
The 5.1 version have the size of 9.4kb but new React Route v6 gonna have size only 2.9kb.
// 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>
);
}
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 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>
);
}
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});
🥳