Editor’s note: This article was last updated 16 November 2022 to include changes made in React Router v6, including the removal of the deprecated useHistory
Hook.
React Hooks were introduced with the release of React v16.8.0 to much excitement. With Hooks, developers can write cleaner components with less boilerplate code in comparison to class components. Many popular React packages are adding support for Hooks so developers can leverage their APIs in functional components.
React Router, the go-to routing solution for React apps, added Hooks support in its v5.0 release. These React Router Hooks provide developers with new ways to handle the router state. In this tutorial, you’ll learn how to use Hooks with React Router and minimize the number of code lines in a component. Let’s get started!
How do Hooks work with React Router?
To demonstrate how Hooks work, we’ll create a React project and set up the pages. To create a new React app, run the following command:
npx create-react-app router-hooks-demo
router-hooks-demo
is the app’s name, but you can name it whatever you want. Next, add the react-router-dom
package:
npm i react-router-dom --save
Import the BrowserRouter
, Link
, Route
, and Routes
components from the react-router-dom
package. We’ll use these components to configure client-side routing in our React app:
import { BrowserRouter, Link, Route, Routes } from "react-router-dom";
For this demo, you’ll only have two routes or pages, the Home
page and the User
page. We’ll mainly be working on the User
page:
const User = () => { return <div>This is the user page</div>; }; const Home = () => { return <div>This is the home page</div>; };
In the App
component, create a navigation bar and add hyperlinks to the Home
and User
pages using React Router’s Link
component. On the webpage, the Link
component is rendered as an <a>
tag:
<nav> <div> <Link to="/">Home</Link> </div> <div> <Link to="/user/:id">User</Link> </div> </nav>
Next, add the Routes
and Route
components, then wrap everything in the BrowserRouter
component:
<BrowserRouter> <nav> <div> <Link to="/">Home</Link> </div> <div> <Link to="/user/:id">User</Link> </div> </nav> <Switch> <Route path="/" element={<Home />} /> <Route path="/user/:id" element={<User />} /> </Switch> </BrowserRouter>
The BrowserRouter
component enables the client-side routing feature and handles the routing logic using the browser history
API. The Route
component renders the page UI when the route path matches the active URL. The Routes
component’s job is to understand its children Route
elements and decide which one to render when a user navigates to different routes.
The final App.js
file should look like the following:
import React from "react"; import { BrowserRouter, Link, Route, Routes } from "react-router-dom"; const User = () => { return <div>This is the user page</div>; }; const Home = () => { return <div>This is the home page</div>; }; export default function App() { return ( <div className="App"> <BrowserRouter> <nav> <div> <Link to="/">Home</Link> </div> <div> <Link to="/user/:id">User</Link> </div> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/user/:id" element={<User />} /> </Routes> </BrowserRouter> </div> ); }
Why are React Router Hooks useful?
Let’s say you need to access the current pathname of the URL inside of a page
component. Before Hooks, you would have to pass the page
component to the component
prop of the Route
component. Then, the Route
component would inject with route props, match
, location
, and history
.
Although this approach works, the component becomes harder to read, and it’s difficult to understand how the props are injected when it comes to maintaining the project. The React Router authors added Hooks support so that the page
components can access history
, location
, and match
objects without having to pass the page
component as a component
prop in the Route
component:
// Route with component prop <Route path="/user/:id" component={User} />; // User component const User = (props) => { const location = props.location; console.log(location.pathname); return <div>This is the user page</div>; };
React Router Hooks
Now that you understand why we add Hooks for routing, let’s see the Hooks in action.
useParams
The useParams
Hook returns an object containing key-value pairs of any parameters that exist in a URL. For example, let’s say you have a User
page component that accepts an id
as a parameter in the URL. You can access the parameter using the useParams
Hook:
import { useParams } from "react-router-dom"; <Route path="/user/:id" element={<User />} /> const User = () => { const params = useParams(); console.log(params); return ( // ... ) }
If you were to pass 234
as the ID in the user URL, /user/234
, the useParams()
Hook would return the following object:
{ id: 234, }
You can display the id
parameter on the page or use it to fetch the user’s information from the server:
import { useParams } from "react-router-dom"; // /user/:id const User = () => { const params = useParams(); return ( <div> <p>This is the user page</p> <p>current user Id - {params.id}</p> </div> ); };
useNavigate
In React Router v6, the useHistory
Hook has been discontinued, providing useNavigate
as its alternative. The main reason for the switch from useHistory
to useNavigate
is to provide better compatibility with React Suspense.
The useNavigate
Hook returns a function that lets you handle route changes and navigate programmatically:
import { useNavigate, useParams } from "react-router-dom"; const User = () => { let navigate = useNavigate(); const params = useParams(); const handleBack = () => { navigate(-1); }; const handleNavigation = () => { navigate("/user/5"); }; return ( <div> <div>This is the user page</div> <div>current user Id - {params.id}</div> <div> <button onClick={handleBack}>Go Back</button> </div> <div> <button onClick={handleNavigation}>Go To Different User</button> </div> </div> ); };
In the code block above, we add two buttons to the User
page component. One button takes the user to the previous page, and another navigates the user to a different page. The navigate(-1)
method is equivalent to clicking on the back button. It navigates the user to the previous page and goes back one entry in the history stack.
useLocation
The useLocation
Hook allows you to access the location
object that represents the active URL. The value of the location
object changes whenever the user navigates to a new URL. The useLocation
Hook can be convenient when you have to trigger any event whenever the URL changes.
Consider that you have to keep track of views on users’ profile pages. You can detect changes in the location
object using the useEffect
Hook, which comes with React:
import { useNavigate, useParams, useLocation } from "react-router-dom"; const User = () => { const navigate = useNavigate(); const params = useParams(); const location = useLocation(); useEffect(() => { console.log(location.pathname); // Send request to your server to increment page view count }, [location]); const handleBack = () => { navigate(-1); }; const handleNavigation = () => { navigate("/user/5"); }; return ( <div> <div>This is the user page</div> <div>current user Id - {params.id}</div> <div> <button onClick={handleBack}>Go Back</button> </div> <div> <button onClick={handleNavigation}>Go To Different User</button> </div> </div> ); };
useMatch
React Router v6 has discontinued the useRouteMatch
Hook, providing the useMatch
Hook as its replacement. useMatch
returns the match data about a route relative to the current route. It matches the active URL with a given path, similar to how the Route
component works.
What problem does the useMatch
hook solve? Say you had a /blog
route where you display multiple blog posts and a blog/:id
route where you display the details of the individual blog posts:
const App = () => { [const posts, setPosts] = useStatte([....]) return ( <Routes> <Route path="/blog/:id" element={<Post post={post} />} /> <Route path="/blog" element={<Posts posts={posts} />} /> <Route path="/" element={<Home />} /> </Routes> ) }
Previously, you would have to pass all the posts to the Post
component and filter out the specific post using the useParams
Hook:
const Post = ({ posts }) => { const id = useParams().id const post = posts.find(post => post.id === Number(id)) return ( <div> <span>{post.user}</span> <p>{post.content}</p> </div> ) }
While this approach works, we can leverage the useMatch
Hook to create a cleaner implementation. The Post
component receives only the data it should display, and not the array of posts.
Use the useMatch
Hook in the app
component:
import { useMatch } from "react-router-dom" const App = () => { [const posts, setPosts] = useStatte([....]) const match = useMatch('/blog/:id') const post = match ? posts.find(post => post.id === Number(match.params.id)) : null return ( <Routes> <Route path="/blog/:id" element={<Post post={post} />} /> <Route path="/blog" element={<Posts posts={posts} />} /> <Route path="/" element={<Home />} /> </Routes> ) }
With this implementation, every time the URL changes, the command below is executed to check if the current route matches the desired URL:
const match = useMatch('/posts/:id')
If the URL matches, the match
variable will return an object containing data about the matching URL. You can then access the id
parameter from the object, use it to filter out the correct post data, and pass it to the Post
component:
const Post = ({ post }) => { return ( <div> <span>{post.user}</span> <p>{post.content}</p> </div> ) }
useRoutes
React Router provides two methods for defining routes in React applications. The first and most popular is using the <Routes>
and <Route>
component. The second approach involves using the useRoutes
Hook and plain JavaScript objects to declare your routes.
To see how the useRoutes
Hook works, start by creating a routes.js
file where you’ll create the routes:
import { useRoutes } from "react-router-dom"; import Home from "./pages/Home"; import Post from "./pages/Post"; import Posts from "./pages/Posts"; import User from "./pages/User"; export default function Router() { let element = useRoutes([ { path: "/home", element: <Home /> }, { path: "/posts", element: <Post /> }, { path: "/post:id", element: <Posts /> }, { path: "user", element: <User /> }, ]); return element; }
Then, import the routes into your App.js
file:
import { BrowserRouter } from "react-router-dom"; import Router from "./routes"; export default function App() { return ( <BrowserRouter> <div className="App"> <h1>useRoutes Example</h1> <Router /> </div> </BrowserRouter> ); }
There are no benefits of using one method for defining routing over another. It boils down to the preferences and patterns you and your team put in place.
Conclusion
Hooks are a great addition to the React ecosystem. In this article, we reviewed some code for handling routing with Hooks, using the useParams
, useNavigate
, useLocation
, useMatch
, and useRoutes
Hooks. Now, you’re ready to take full advantage of what they have to offer.
Get setup with LogRocket's modern React error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
I was following this as a codealong, start is a bit confusing maybe add that the changes are made in App.js.
But I get a compile error at the useEffect() part, until I realized I still had to import it from react.
I’m just 2 weeks in coding in React, so guess for a beginner they will need some more guidance
Did they change something in the ‘react-router-dom’ npm? because it says Switch is not exported from ‘react-router-dom’