createBrowserRouter A step up from Switch
Routing is how you navigate in a web application. It allows you to map URL paths to specific components, enabling users to access various parts of your app without needing to reload the page. Routing is important because it ensures a seamless user experience, making your app feel similar to a desktop application. It helps in organizing and managing different views, maintaining a clean and efficient codebase, and enabling features like deep linking and dynamic content loading. Few years back we were using Switch
but now we have createBrowserRouter.
Back in the day, we used the Switch
component to handle routing in React. It was straightforward and did the job well by rendering only the first child <Route>
that matched the location.
Here’s a simple example using Switch
:
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</Router>
);
}
export default App;
Pretty neat, right? But as our apps grew more complex, Switch
started to show its limitations.
One major limitation of Switch
was its lack of built-in support for nested routes. In complex applications, it's common to have layouts where some components remain constant (like headers or sidebars) while others change based on the route. With Switch
, achieving this required cumbersome workarounds and additional code, making the routing setup harder to manage and maintain.
Error handling was another area where Switch
fell short. If a user navigated to a non-existent route, handling this gracefully required extra effort. Typically, developers had to add a catch-all route at the end of the Switch
component, which wasn't always intuitive or straightforward.
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route component={NotFound} /> {/* Catch-all route for 404 errors */}
</Switch>
For larger applications, the Switch
setup could become quite verbose and repetitive. Each route had to be defined explicitly within the Switch
block, leading to cluttered and less maintainable code. This verbosity made it harder to manage and refactor routes, especially as the application grew.
Handling data fetching and route-based data loading wasn't straightforward with Switch
. Developers often had to rely on external libraries or custom hooks to fetch data before rendering a component, leading to fragmented code and a less cohesive approach to routing and data management.
As React evolved with new features like hooks and concurrent rendering, Switch
didn't keep pace. Integrating these modern features into the routing logic required additional effort and custom solutions, making it harder to leverage the full power of React's latest advancements.
With the release of React Router version 6, we got createBrowserRouter
. It’s packed with powerful features that make routing in React apps easier and more efficient.
npm install react-router-dom
# or
yarn add react-router-dom
Now, let’s get into setting up createBrowserRouter
.
- Import Necessary Functions:
Start by importing the required functions from react-router-dom
:
import { createBrowserRouter, RouterProvider } from "react-router-dom";
- Define Routes:
Next, define the routes for your application. Here’s a simple example:
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "about",
element: <About />,
},
{
path: "contact",
element: <Contact />,
},
]);
In this setup, we have three routes: /
, /about
, and /contact
, each rendering a different component.
To render the router, use the RouterProvider
component and pass in the router object:
function App() {
return <RouterProvider router={router} />;
}
export default App;
Nested routes are super handy for managing complex applications. They let you define routes within other routes, which is great for layouts where some components are always visible (like a navigation bar) while others change based on the route.
Here’s how you can define nested routes:
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
children: [
{
path: "about",
element: <About />,
},
{
path: "contact",
element: <Contact />,
},
],
},
]);
In this example, About
and Contact
are nested within the Home
route.
Need to capture values from the URL? Route parameters have got you covered. This is especially useful for dynamic routing, like displaying user profiles or articles based on an ID.
import UserProfile from "./components/UserProfile";
const router = createBrowserRouter([
{
path: "/user/:userId",
element: <UserProfile />,
},
]);
In this setup, the UserProfile
component will receive a userId
parameter from the URL, which can be accessed within the component using hooks like useParams
.
You can define error handling routes to display custom error pages for specific conditions:
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
errorElement: <NotFound />,
children: [
{
path: "about",
element: <About />,
},
{
path: "contact",
element: <Contact />,
},
],
},
]);
In this example, if a route is not found, the NotFound
component will be rendered.
React Router 6 introduces the ability to define loaders and actions for routes, which can handle data fetching and mutations. This ensures data is loaded before a component is rendered:
import { createBrowserRouter } from "react-router-dom";
import Home from "./components/Home";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
loader: async () => {
const data = await fetchData();
return { data };
},
},
]);
async function fetchData() {
// Fetch your data here
return fetch("/api/data").then((res) => res.json());
}
In this example, fetchData
is called before rendering the Home
component, ensuring the component has the necessary data.
Organize your routes in a separate file to keep your code clean and maintainable. For example, create a routes.js
file:
// routes.js
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";
const routes = [
{
path: "/",
element: <Home />,
children: [
{
path: "about",
element: <About />,
},
{
path: "contact",
element: <Contact />,
},
],
},
];
export default routes;
Then, import and use this in your main file:
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import routes from "./routes";
const router = createBrowserRouter(routes);
function App() {
return <RouterProvider router={router} />;
}
export default App;
For better performance, use lazy loading to load components only when needed. React’s lazy
and Suspense
can help with this:
import React, { lazy, Suspense } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const Home = lazy(() => import("./components/Home"));
const About = lazy(() => import("./components/About"));
const Contact = lazy(() => import("./components/Contact"));
const router = createBrowserRouter([
{
path: "/",
element: (
<Suspense fallback={<div>Loading...</div>}>
<Home />
</Suspense>
),
children: [
{
path: "about",
element: (
<Suspense fallback={<div>Loading...</div>}>
<About />
</Suspense>
),
},
{
path: "contact",
element: (
<Suspense fallback={<div>Loading...</div>}>
<Contact />
</Suspense>
),
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
createBrowserRouter
is a big step up from the traditional Switch
component, offering enhanced flexibility and functionality. With features like nested routes, route parameters, error handling, and data fetching, you can create dynamic and efficient routes for your projects.
For more info refer the documentation
Related articles
Development using CodeParrot AI