Difficulty: Intermediate
React Router is the standard routing library for React applications. It enables client-side routing - navigating between different views without full page reloads. Instead of the browser requesting a new HTML page from the server, React Router intercepts navigation, updates the URL, and renders the appropriate component. This creates a fast, app-like experience while maintaining proper URL semantics.
The core components of React Router v6 are BrowserRouter, Routes, Route, and Link. BrowserRouter wraps your entire application and provides the routing context using the browser's History API. It must be placed at the top of your component tree (typically in App or index). Routes is a container that evaluates its child Route elements and renders the first one that matches the current URL. Route defines a mapping between a URL path and a component.
The Link component replaces HTML anchor tags (<a>) for internal navigation. While <a href="/about"> would cause a full page reload, <Link to="/about"> handles the navigation client-side - it updates the URL and triggers a re-render of the Routes, which then matches and renders the appropriate component. Link is essential for the single-page application experience.
Route elements use a path prop to specify the URL pattern and an element prop to specify the component to render. In React Router v6, the element prop takes a JSX element (not a component reference): <Route path="/about" element={<About />} />. This change from v5 (which used component or render props) simplifies data passing because you can pass props directly to the element.
React Router v6 uses relative paths and automatic route ranking. Routes are automatically matched by specificity, so you don't need to worry about ordering. A path of "/users/new" is more specific than "/users/:id", so it matches first regardless of declaration order. The index route (no path or path="") renders at the parent route's URL and serves as the default child.
// Simulating React Router components
function BrowserRouter({ children }) {
console.log('BrowserRouter: providing routing context');
return children;
}
function Routes({ children }) {
console.log('Routes: evaluating route matches');
return children;
}
function Route({ path, element }) {
console.log('Route defined: path=' + path);
return null;
}
function Link({ to, children }) {
console.log('Link: navigates to ' + to + ' (' + children + ')');
return null;
}
// Usage pattern
function App() {
console.log('App with routing:');
BrowserRouter({
children: Routes({
children: [
Route({ path: '/', element: 'Home' }),
Route({ path: '/about', element: 'About' }),
Route({ path: '/contact', element: 'Contact' })
]
})
});
Link({ to: '/', children: 'Home' });
Link({ to: '/about', children: 'About' });
return null;
}
App();
BrowserRouter wraps the app, Routes contains all route definitions, each Route maps a path to a component, and Link creates navigation links without page reloads.
function matchRoute(currentPath, routes) {
console.log('Current URL:', currentPath);
for (const route of routes) {
if (currentPath === route.path) {
console.log('Matched route:', route.path, '-> Renders:', route.component);
return route.component;
}
}
console.log('No match -> Renders: 404 Not Found');
return '404';
}
const routes = [
{ path: '/', component: 'HomePage' },
{ path: '/about', component: 'AboutPage' },
{ path: '/contact', component: 'ContactPage' }
];
matchRoute('/', routes);
matchRoute('/about', routes);
matchRoute('/unknown', routes);
React Router evaluates routes and renders the matching component. When no route matches, you can define a catch-all route with path='*' to show a 404 page.
function NavigationComparison() {
console.log('--- HTML anchor <a> ---');
console.log('Causes full page reload: true');
console.log('Fetches new HTML from server: true');
console.log('Loses React state: true');
console.log('--- React Router <Link> ---');
console.log('Causes full page reload: false');
console.log('Fetches new HTML from server: false');
console.log('Loses React state: false');
console.log('Updates URL via History API: true');
console.log('Triggers route re-evaluation: true');
return null;
}
NavigationComparison();
Link enables client-side navigation without page reloads. The browser URL updates, but only the matched route's component re-renders. Application state is preserved across navigations.
BrowserRouter, Routes, Route, Link, client-side routing