createBrowserRouter A step up from Switch

Gautam Vaja - May 18 - - Dev Community

What is Routing? Why Do We Need It? 🧭

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.

The Good Ol' Days of Switch

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;
Enter fullscreen mode Exit fullscreen mode

Pretty neat, right? But as our apps grew more complex, Switch started to show its limitations.

Problems and Limitations of Switch 😬

1. Lack of Nested Routes

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.

2. Limited Error Handling

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>
Enter fullscreen mode Exit fullscreen mode

3. Verbose and Repetitive Code

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.

4. Limited Data Fetching Capabilities

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.

5. Poor Integration with Modern React Features

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.

Say Hello to createBrowserRouter 👋

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.

Setting Up createBrowserRouter

npm install react-router-dom
# or
yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode

Now, let’s get into setting up createBrowserRouter.

Basic Setup

  1. Import Necessary Functions:

Start by importing the required functions from react-router-dom:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
Enter fullscreen mode Exit fullscreen mode
  1. 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 />,
  },
]);
Enter fullscreen mode Exit fullscreen mode

In this setup, we have three routes: /, /about, and /contact, each rendering a different component.

Rendering the Router

To render the router, use the RouterProvider component and pass in the router object:

function App() {
  return <RouterProvider router={router} />;
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Cool New Features in createBrowserRouter 🤩

Nested Routes

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 />,
      },
    ],
  },
]);
Enter fullscreen mode Exit fullscreen mode

In this example, About and Contact are nested within the Home route.

Route Parameters

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 />,
  },
]);
Enter fullscreen mode Exit fullscreen mode

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.

Error Handling

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 />,
      },
    ],
  },
]);
Enter fullscreen mode Exit fullscreen mode

In this example, if a route is not found, the NotFound component will be rendered.

Data Fetching

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());
}
Enter fullscreen mode Exit fullscreen mode

In this example, fetchData is called before rendering the Home component, ensuring the component has the necessary data.

Best Practices

Organize Routes

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Use Lazy Loading

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;
Enter fullscreen mode Exit fullscreen mode

Conclusion

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .