Peter Ekene Eze Learn, Apply, Share

How React Hooks can replace React Router

7 min read 2146

Since the advent of React Hooks, a lot of things have changed. Some things we didn’t have issues with before have started causing concern. The features and possibilities that come with Hooks have redefined how we approach certain concepts in React, and routing happens to be one of them.

Before we proceed, I would like to mention that this post is not intended in any way to take shots at React Router or belittle its importance. Instead, we are going to explore other possibilities and look at how we can improve the routing experience in React apps using hooks.

To this effect, we’ll be making references to the React Router and also to hooksrouter for demonstration purposes. First, let’s take a closer look at React Router.

React Router

React Router is a popular declarative way of managing routes in React applications. It takes away all of the stress that comes with manually setting routes for all of the pages and screens in your React application. React Router exports three major components that help us make routing possible — Route, Link, and BrowserRouter.

Routing In React Router

If you were building a React app and you had three pages, here’s how you would conventionally implement routing with React Router:

import Users from "./components/Users";
import Contact from "./components/Contact";
import About from "./components/About";
function App() {
  return (
    <div>
      <Router>
        <div>
          <Route path="/about" component={About} />
          <Route path="/users" component={Users} />
          <Route path="/contact" component={Contact} />
        </div>
      </Router>
    </div>
  );
}

The <Route/> component imported from the React Router package takes in two props, the path to direct the user to the specified path and the component to define the content in the said path.

The Hooks alternative to routing

All thanks to Chris Engel for the hookrouter tool that we will be focusing on to drive home these demonstrations. The hookrouter module exports a useRoutes() hook that evaluates a predefined routes object and returns a result. In the routes object, you define your routes as keys with their values as functions that will be called when the routes match. Here’s a practical demonstration:

import React from "react";
import Users from "./components/Users";
import Contact from "./components/Contact";
import About from "./components/About";
const routes = {
  "/": () => <Users />,
  "/about": () => <About />,
  "/contact": () => <Contact />
};
export default routes;

Personally, I like this method. Why? Well, because we didn’t have to do so much work. With React Router we had to render the <Route/> component for all the individual routes in our app. Not to mention all of the props we passed to it. Back to hooks, we can use this defined Routes in our app by simply passing it to the useRoutes() hook:

import {useRoutes} from 'hookrouter';
import Routes from './router'

function App() {
  const routeResult = useRoutes(Routes)
  return routeResult
}

And this gives us exactly the same result we’d get with the React Router routing demonstration but with a cleaner and lighter implementation.

We made a custom demo for .
No really. Click here to check it out.

React Router navigation

React Router also gives us access to the <Link/> component. It helps us customize route navigations and manage interactive routing in React apps. We have a react app with three routes, let’s render the routes on screen and navigate to them when clicked:

import { Route, Link, BrowserRouter as Router } from "react-router-dom";
import Users from "./components/Users";
import Contact from "./components/Contact";
import About from "./components/About";

function App() {
  return (
    <div className="App">
      <Router>
        <div>
          <ul>
            <li>
              <Link to="/about">About</Link>
            </li>
            <li>
              <Link to="/users">Users</Link>
            </li>
            <li>
              <Link to="/contact">Contact</Link>
            </li>
          </ul>
          <Route path="/about" component={About} />
          <Route path="/users" component={Users} />
          <Route path="/contact" component={Contact} />
        </div>
      </Router>
    </div>
  );
}

This creates the navigations we need to go from one page to another within the app. Here’s a visual representation of what we are doing here.

The Hooks alternative to React navigation

The hookrouter module provides a wrapper around the HTML anchor tag <a/> as <A/>. It is accessible as a react component and 100% feature compatible to the native <a/> tag. The only difference is that it pushes navigations to the history stack instead of actually loading a new page.

const routes = {
  "/user": () => <Users />,
  "/about": () => <About />,
  "/contact": () => <Contact />
};

function App() {
  const routeResult = useRoutes(routes);
  return (
    <div className="App">
      <A href="/user">Users Page</A>
      <A href="/about">About Page</A>
      <A href="/contact">Contacts Page</A>
      {routeResult}
    </div>
  );
}

Programmatic navigation

The hookrouter module gives us access to a navigate() hook function that we can pass a URL to and it will navigate the user to that URL. Every call to the navigate() function is a forward navigation, as a result, users can click the browser’s back button to return to the previous URL.

navigate('/user/');

This happens by default. However, if you need a different behavior, you can do a replace navigation. How? you might ask, well the navigation() hook primarily takes-in three parameters — navigate(url, [replace], [queryParams]) the second parameter is used to effect the replace behavior. It erases the current history entry and replaces it with a new one. To achieve that effect, simply set its argument to true.

navigate('/user', true);

React Router switch

Conventionally, React Router uses the <Switch/> component to render a default page when the defined navigation routes are not matched. Usually, it renders a 404 page to let the user know that the selected route is not defined in the application. To do this, we wrap all the rendered routes inside the <Switch/> component and render the 404 page without defining a path prop for it:

import { Route, Link, BrowserRouter as Router, Switch } from "react-router-dom";
import Users from "./components/Users";
import Contact from "./components/Contact";
import Home from "./components/About";
import NoPageFound from "./components/NoPageFound.js";

function App() {
  return (
    <div className="App">
      <Router>
        <div>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/users">Users</Link>
            </li>
            <li>
              <Link to="/contact">Contact</Link>
            </li>
          </ul>
          <Switch>
            <Route exact path="/" component={Home} />
            <Route path="/users" component={Users} />
            <Route path="/contact" component={Contact} />
            <Route component={NoPageFound} />
          </Switch>
        </div>
      </Router>
    </div>
  );
}

This way, whenever an undefined path is reached, React Router renders the NopageFound component. It is a very keen way of letting users know where they are and what is going on at all times while navigating your React site.

The Hooks alternative to switch

Because we define a routes object that holds all our route paths, and simply pass that object into the useRoutes() hook, it becomes really straightforward to conditionally render routes. If we define a NoPageFound file to render by default when a selected route is not defined, we’ll only need to pass that file for rendering alongside our result function like so:

import { useRoutes, A } from "hookrouter";
import routes from "./router";
import NoPageFound from "./components/NoPageFound";
function App() {
  const routeResult = useRoutes(routes);
  return (
    <div className="App">
      <A href="/user">Users Page</A> <br />
      <A href="/about">About Page</A>
      <br />
      <A href="/contact">Contacts Page</A> <br />
      {routeResult || <NoPageFound />}
    </div>
  );
}

Compared to using the <Switch> component in React Router to render default pages, I think this seems a bit cleaner and more readable.

React Router redirects

Redirection happens when we want to dynamically direct a user from one route to another. For instance, during login, when a user successfully logs in, we would want to redirect them from the ('/login') route to the ('/dashboard') route.

With React Router, we can do this in a few ways — using the history object or the <Redirect/> component. For instance, If we have a login form, we can leverage the browser’s history object to push the user to the '/dashboard' route when are logged in:

import React from 'react'
class Login extends React.Component {
  loginUser = () => {
  // if (user is logged in successfully)
    this.props.history.push('/dashboard')
  }
  render() {
    return (
      <form>
        <input type="name" />
        <input type="email" />
        <button onClick={this.loginUser}>Login</button>
      </form>
    )
  }
}
export default Login

Consequently, we can also use the <Redirect/> component available in React Router to dynamically redirect users.

The Hooks alternative to redirects

The hookrouter module exports a useRedirect() hook that can take a source route and a target route as parameters.

useRedirect('/user', '/dashboard');

This will automatically redirect users to the '/dashboard' route whenever the '/user' path is matched. For instance, if we didn’t want to show any users but instead redirect a user automatically from the to their '/dashboard', we would define our app like this:

import {useRoutes, useRedirect} from 'hookrouter';
import dashboard from "./components/Dashboard";
const routes = {
    '/home': () => <Users />,
    '/dashboard': () => <Dashboard />
};
const Users = () => {
    useRedirect('/user', '/dashboard');
    const routeResult = useRoutes(routes);
    return routeResult
}

Here’s the visual output of this process:

It is worthy to note that the useRedirect() hook triggers a replacement navigation intent. As a result, there will only be one entry in the navigation history. This means that if redirection happens from '/user' to '/dashboard' as we showed in the last snippet, the '/user' route will not appear in the browsing history. We will only have the '/dashboard' route.

Handling URL parameters with React Router

URL parameters help us render components based on their dynamic URL’s. It works in a similar fashion with nested routes however, in this case, the routes are not exactly changing, rather they’re updating.

For instance, if we had different users in our app, it would make sense to identify them separately with their individual routes like 'user/user1/' and 'users/user2/' etc. To do that, we’ll need to use URL parameters. In React Router, we simply pass a placeholder (like id) starting with a colon to the path prop in the <Route/> component:

<Route path="users/:id" component={Users} />

Now, if you navigate to 'users/1' on the browser, this particular user will be available in your Users.js prop.

The Hooks alternative to handling URL parameters

There’s not much difference in the way the hookrouter treats URL parameters as compared to React Router. The construct is the same (i.e you can pass your URL parameters to your target routes using a colon and the parameter name).

However, there’s still a difference in the way the route hook works. It reads all URL parameters and puts them into an object. It does this using the keys you defined in the routes object. Then all the named parameters will be forwarded to your route result function as a combined object.

const routes = {
  '/user/:id': ({id}) => <User userId={id} />
}

Using object destructuring, we simply take the id property from the props object and then apply it onto our component. That way, we achieve exactly the same result as we did with the React Router alternative.

Conclusion

Like I said at the beginning of this post, the intention is to offer you an alternative way of routing in your React projects. React Router is a great tool however, I think with the arrival of Hooks, a lot of things have changed in React and that also includes how routing works. This module based on Hooks offers a more flexible and cleaner way if handling routes in smaller projects. If you like trying out new tools as much as I do, I encourage you to give it a shot. There are so many other aspects we haven’t covered yet in this post, like how both tools handle nested routing, etc. Feel free to learn more about the hookrouter module here.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Peter Ekene Eze Learn, Apply, Share

5 Replies to “How React Hooks can replace React Router”

  1. In your first hooks routing code sample, the o e for routes.js, you forgot to reference each individual component. You import them but they’re not in each of the functions for that Routes object. So the 3 functions return nothing.

  2. How would you setup your hooks router to support modal side panels (which slides out when url starts from /modal/.. for example)? It this possible to describe multiple switches (one will return previous url component, and another one will return modal url component) within one rules object? Is this possible to forcibly pass desired url to router like <Switch location={previousUrl}?

  3. This was a very helpful article thank you. I have encountered one issue with this though. My hook components keep getting called. using a components in a menu placing a break point in the export default function PaymentSettings(props) for example – will result in that breakpoint getting it continuously.

  4. This is cleaner compared to Routes usage for sure. However an issue that keeps coming up in larger applications is the number of components imported for a “routes” file, as it needs each component to render for every path defined. So the more routes you have the more components need importing, I’ve seen 1000’s of lines before with no options outside creating smaller modules or a key based component directory. It would be nice to see this tackled in some other way, if you have any ideas how hooks might help here that would be great?

    Thanks
    JB

Leave a Reply