
Navigating Web Applications with React Router

Table of contents
- What is React Router?
- Core Components of React Router
- Protected Routes and Authentication
- Best Practices with React Router
- Integrating with Redux or Context API
- Performance Optimization
- Testing Components with Routing
In modern web development, creating single-page applications (SPAs) with smooth navigation is crucial for a good user experience. React Router, a popular library in the React ecosystem, provides a robust solution for handling navigation in SPAs. This article offers an in-depth exploration of React Router, discussing its core features, how it works, and best practices for implementing it in your React applications.
What is React Router?
React Router is a standard library for routing in React. It enables the navigation among views of various components in a React Application, allows changing the browser URL, and keeps the UI in sync with the URL.
CRS Info Solutions stands out for its exceptional React.js training in Hyderabad, tailored specifically for students. Their program focuses on practical, hands-on learning, ensuring that students not only understand React.js training Bangalore concepts but also apply them effectively in real-world scenarios. This approach has established CRS Info Solutions as a go-to destination for aspiring React.js developers in the region.
Explore: How to start with React js learning?
Why Use React Router?
React Router is a powerful routing library for React that allows developers to create dynamic, single-page applications (SPAs). Here are two key reasons to use React Router:
Seamless Navigation and URL Management:
React Router enables seamless navigation between different components in your application without the need for page reloads, which provides a smooth and consistent user experience. It also helps in managing the browser history and synchronizing the UI with the URL, making it easy to implement deep linking and bookmarking.
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
Explore: Introduction to React
Dynamic Routing:
React Router supports dynamic routing, which means routes can be defined and modified based on the application’s state or user interactions. This allows developers to build more flexible and interactive applications. The library handles the complexities of routing automatically, enabling you to define routes declaratively anywhere in your component tree.
import { BrowserRouter as Router, Route, useParams } from 'react-router-dom';
function UserDetails() {
let { userId } = useParams();
return <h2>User ID: {userId}</h2>;
}
function App() {
return (
<Router>
<div>
<h1>Users</h1>
<Route path="/user/:userId">
<UserDetails />
</Route>
</div>
</Router>
);
}
Explore: Creating a Sample Service in React JS
Core Components of React Router
React Router’s core components are essential for defining routing behavior in React applications. Here’s an overview of the core components along with a cohesive code example that demonstrates each component in use:
- BrowserRouter: This component uses the HTML5 history API to keep your UI in sync with the URL. It is the router component that should be used for web applications.
- Route: This is perhaps the most important component of React Router. It renders a UI component depending on the URL path. It takes a
path
prop to match the URL and acomponent
orrender
method to specify what should be rendered when the path matches. - Link: This component is used to create links in your application. It works like an
<a>
tag in HTML but is aware of the Router it’s wrapped in, allowing it to navigate without refreshing the page. - Switch: This component is used to group
<Route>
components and renders the first one that matches the current URL. It is useful for exclusivity in route matching, ensuring that only one route is rendered at a time.
Explore: React JSX
Here’s an example that integrates all these components:
import React from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
function Home() {
return <h1>Home Page</h1>;
}
function About() {
return <h1>About Page</h1>;
}
function Users() {
return <h1>Users List</h1>;
}
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/users">Users</Link></li>
</ul>
</nav>
<Switch>
<Route path="/about" component={About} />
<Route path="/users" component={Users} />
<Route path="/" exact component={Home} />
</Switch>
</div>
</Router>
);
}
export default App;
In this example:
- BrowserRouter wraps the entire application to enable dynamic routing.
- Link elements are used to navigate between pages, ensuring that navigation is handled client-side.
- Switch is used to select the first
<Route>
that matches the current path, which helps in avoiding the rendering of multiple components when paths overlap. - Route components define the rendering logic based on the path. The
exact
prop on the root path (/
) ensures it matches exactly and doesn’t interfere with other routes.
Explore: How Can You Master Programmatically Navigation in React Router?
Setting Up React Router
To use React Router, start by installing it in your React project:
npm install react-router-dom
Then, set up the basic routing in your application:
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
function App() {
return (
<Router>
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
</div>
</Router>
);
}
Explore: React Components
Dynamic Routing
React Router allows for dynamic routing, where components are rendered based on the URL parameters. This is particularly useful for things like user profiles or product pages.
Example of dynamic routing:
<Route path="/user/:id" component={User}
Explore: Props and State in React
Nested Routing
Nested routing in React Router allows you to define routes within routes, enabling a more organized and hierarchical structure in your application. This is particularly useful for apps with complex UIs where different sections have multiple layers of navigation. Here’s a simple example using React Router v5 to illustrate nested routing:
import React from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
function App() {
return (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/products">Products</Link></li>
</ul>
<Switch>
<Route path="/products" component={Products} />
<Route path="/" exact component={Home} />
</Switch>
</div>
</Router>
);
}
function Products({ match }) {
return (
<div>
<h2>Products</h2>
<ul>
<li><Link to={`${match.url}/laptop`}>Laptop</Link></li>
<li><Link to={`${match.url}/smartphone`}>Smartphone</Link></li>
</ul>
Explore: How Can You Pass Props to Children Components in React?
Explanation:
- App Component: This is the main component where the
Router
is defined. It includes links to the Home page and the Products section. - Products Component: This serves as a sub-route under the main route. It further contains links to individual products like laptops and smartphones. The
match.url
is used to build relative links, andmatch.path
is used to construct nested routes. - ProductDetail Component: This component displays details for a specific product. It captures the
productId
parameter from the URL to display relevant information. - Home Component: This simply renders the Home page.
Explore: Lifecycle Methods in React
Protected Routes and Authentication
Protected routes are an essential feature in applications that require user authentication. They ensure that certain UI parts are only accessible to authenticated users. React Router does not include built-in support for protected routes, but you can easily implement them using a higher-order component or a wrapper component that checks for user authentication status before rendering the intended component.
Here’s an example of how to implement protected routes in a React application using React Router v5:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect, Link } from 'react-router-dom';
// Mock authentication service
const authService = {
isAuthenticated: false,
login(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
logout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100); // fake async
}
};
function App() {
return (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Switch>
<Route path="/" exact component={Home} />
<ProtectedRoute path="/protected" component={Protected} />
</Switch>
</div>
</Router>
);
}
function Home() {
return <h1>Home Page</h1>;
}
function Protected() {
return <h1>Protected Page</h1>;
}
// ProtectedRoute component
function ProtectedRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props =>
authService.isAuthenticated ? (
Explore: Event Handling in Reactjs
Handling 404 Pages
To handle 404 pages, you can use the Route
component with no path:
<Route component={NotFound} />
Best Practices with React Router
1. Use <Switch>
to Exclusively Match Routes
Using a <Switch>
component ensures that only the first child <Route>
or <Redirect>
that matches the location is rendered. This is particularly useful for avoiding multiple route matches and for handling not-found (404) routes gracefully.
Example:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
function App() {
return (
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/users" component={Users} />
<Route component={NotFound} />
</Switch>
</Router>
);
}
function Home() {
return <h1>Home Page</h1>;
}
function About() {
return <h1>About Page</h1>;
}
function Users() {
return <h1>Users List</h1>;
}
function NotFound() {
return <h1>404 Not Found</h1>;
}
Explore: Conditional Rendering in React
2. Parameterized Routing for Flexibility
Using parameterized routes allows you to build more flexible and dynamic applications. It’s particularly useful for cases like user profiles, edit pages, or any instance where specific data needs to be retrieved based on the URL.
Example:
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
function App() {
return (
<Router>
<Route path="/profile/:userId" component={UserProfile} />
</Router>
);
}
function UserProfile({ match }) {
return <h1>Profile Page for User {match.params.userId}</h1>;
}
Explore: Form Handling in React JS
3. Lazy Loading Components with React.Suspense
For larger applications, it’s a good practice to lazy load components to split the bundle into smaller chunks and only load them when needed. This reduces the initial load time and enhances user experience.
Example:
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
}
Explore: Component Composition in React
Integrating with Redux or Context API
Integrating React Router with Redux or the Context API can greatly enhance state management across your routing logic, especially for large applications. Below, I’ll provide an example of how to integrate React Router with the Context API, as it’s a built-in part of React and doesn’t require additional libraries like Redux.
Integrating React Router with the Context API
The Context API allows you to pass data through the component tree without having to pass props down manually at every level. This is particularly useful for managing authenticated user states, theme data, or any other globally needed data.
Explore: React Hooks: Revolutionizing Functional Components
Example: User Authentication State with Context API and React Router
Here’s a complete setup that shows how to use the Context API to manage user authentication states across different routes:
import React, { createContext, useContext, useState } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect, Link } from 'react-router-dom';
// Create a context
const AuthContext = createContext(null);
// A hook to use the auth context
const useAuth = () => useContext(AuthContext);
// Authentication provider component
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = user => {
setUser({ id: user.id, name: user.name });
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
// Protected Route Component
const ProtectedRoute = ({ component: Component, ...rest }) => {
const { user } = useAuth();
return (
<Route
{...rest}
render={props =>
user ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
};
// Application Components
const Home = () => <h1>Home Page</h1>;
const About = () => <h1>About Page</h1>;
const Login = () => {
const { login } = useAuth();
const handleLogin = () => {
login({ id: 1, name: "John Doe" });
};
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>Log in</button>
</div>
);
};
function App() {
return (
<AuthProvider>
<Router>
<div>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Switch>
<Route path="/login" component={Login} />
<ProtectedRoute path="/about" component={About} />
<ProtectedRoute path="/" exact component={Home} />
</Switch>
</div>
</Router>
</AuthProvider>
);
}
export default App;
Explanation:
- AuthContext & useAuth: Creates a context for authentication state and a custom hook for accessing the context.
- AuthProvider: Provides authentication state and functions to modify it to the rest of your application.
- ProtectedRoute: A component that checks for authentication before rendering the requested route. If not authenticated, it redirects to a login page.
- Home, About, Login: Components representing different pages. Login changes the authentication state, which affects routing due to the
ProtectedRoute
.
Explore: Step-by-Step Guide to React’s Context API
Accessibility Considerations
Semantic HTML: Use semantic HTML elements to enhance the accessibility of your website. Semantic elements like <button>
, <header>
, <footer>
, <nav>
, and <main>
provide meaningful information about the type of content and structure to assistive technologies like screen readers. This helps in making the content more navigable and understandable.
<nav>
<!-- Screen readers understand that this is a navigation section -->
</nav>
<main>
<!-- Main content area, helping users find the core content quickly -->
</main>
Keyboard Navigation: Ensure that all interactive elements are accessible via keyboard. Users who cannot use a mouse should be able to navigate through your site using keyboard shortcuts. This involves proper tab indexing and managing focus states for elements like links, buttons, form inputs, and custom widgets.
<button tabIndex={0} onKeyPress={handleKeyPress}>
Click me
</button>
ARIA (Accessible Rich Internet Applications) Roles and Properties: Use ARIA roles and properties to enhance accessibility when HTML semantics alone are insufficient. ARIA roles describe the type of widget presented and the actions it can perform, while properties provide states and values to the roles. However, it’s important to use ARIA as a last resort, after you have used native HTML elements.
<div role="button" aria-pressed="false">
<!-- Custom button accessible to assistive technologies -->
</div>
Explore: React Router Interview Questions
Performance Optimization
React Router can impact performance if not used properly. Avoid unnecessary re-renders and complex route structures that can slow down your application.
Code Splitting: This technique involves splitting your application’s code into smaller, manageable chunks that are loaded only when needed. This can significantly reduce the initial load time and the amount of code processed and loaded during critical interactions. React supports code splitting out of the box with dynamic import()
statements, which can be used with React’s lazy
function and Suspense
component.
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
Efficient Resource Loading: Optimize how resources are loaded by prioritizing critical assets and deferring non-critical ones. This includes practices like minifying JavaScript, CSS files, and images, using efficient formats for media files (e.g., WebP for images, AV1 for video), and leveraging modern loading strategies such as lazy loading for images and asynchronous script loading.
<img src="example.jpg" loading="lazy" alt="Example" />
<script src="non-critical.js" async></script>
Explore: Understanding React.js Props and State with Practical Examples
Use of Memoization and Avoiding Unnecessary Rerenders: React’s rendering behavior can be optimized by preventing unnecessary re-renders of components. Utilize React.memo
for functional components, shouldComponentUpdate
lifecycle method for class components, or hooks like useMemo
and useCallback
to memoize calculations and functions. This prevents expensive recalculations and re-renders when the input data has not changed.
import React, { useMemo } from 'react';
const ExpensiveComponent = ({ value }) => {
const computedValue = useMemo(() => computeExpensiveValue(value), [value]);
return <div>{computedValue}</div>;
};
Testing Components with Routing
Testing React components that involve routing requires a bit of setup to mock the router environment, ensuring that your components can operate as if they are part of a larger application. For this purpose, libraries such as React Testing Library and Jest are commonly used to simulate routing.
Below is an example of how you can test a React component that uses React Router. This test will verify whether the correct content is rendered based on the route.
Component Setup
First, let’s define a simple component with React Router:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
function App() {
return (
<Router>
<div>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return <h1>Home Page</h1>;
}
function About() {
return <h1>About Page</h1>;
}
export default App;
Testing the Component
Now, let’s write a test for the App
component using React Testing Library to ensure that the correct page content shows up for different routes:
import React from 'react';
import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
import { createMemoryHistory } from 'history';
import { Router as MemoryRouter } from 'react-router-dom';
describe("Routing tests", () => {
test('should render the home page by default', () => {
render(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
);
expect(screen.getByText('Home Page')).toBeInTheDocument();
});
test('should render the about page on "/about"', () => {
render(
<MemoryRouter initialEntries={['/about']}>
<App />
</MemoryRouter>
);
expect(screen.getByText('About Page')).toBeInTheDocument();
});
});
Explanation
- MemoryRouter: This component from React Router is used in testing environments to simulate navigating to different routes. It doesn’t manipulate the browser URL but provides the same functionality as
BrowserRouter
. - initialEntries: This prop on
MemoryRouter
is used to set the initial route for each test, making it possible to test how the component behaves on different routes. - React Testing Library: The
render
function is used to render the component, andscreen.getByText
is used to assert that the expected text exists in the document.
React Router is an essential tool for building SPAs with React. It offers a flexible and efficient way to handle navigation and URL management, enhancing the user experience of web applications. Understanding and implementing React Router effectively can significantly improve the structure and navigability of your React applications, making them more intuitive and user-friendly. As you build more complex applications, React Router will continue to be an invaluable tool in your React development toolkit.
Enroll for our real time job oriented React js training in Hyderabad, kick start today!