React Routing Without React Router

React

07/11/2020


Keep things simple, stupid! The motto of every developer, I hope... This can also be applied to the use of external libraries, which offer a plethora of awesome features and make our developer hearts go fuzzy. Yet, more often than not, we might not be able to take advantage of even a fraction of them. In that case, is it even worth including them at the expense of a more bloated web application? Well, not always... 😟 In this article, I'd like to demonstrate how simple React routing can be done in React using a custom solution instead of using an external library such as React Router.

A word of caution

Hey there! Before we move on, did you see how I made the word simple earlier 👆 bold? Well, that's what this solution is intended for. If you've only got a couple of pages you need routing for, then you'll surely benefit from a custom solution. But anything more complex than that? You'll probably want to start considering a more powerful tool 💪 like React Router where you can take advantage of its powerful features. Capiche?!

Meme about people using React Router

Billy, such a savage... 😏

The setup

The example I will go through in this article can be accessed on my GitHub repository. The project was created from the Create React App template and consists of three different pages: Home, About and Contact.

The routing logic

In Routing.js, the main part of our routing logic can be seen. Take note ⚠️ that this custom router uses React hooks like useState and useMemo as well as the Context API. If you aren't familiar with them, I recommend you brush up your knowledge about them before tackling the code below.

Now, without getting too hung up on the details 🧐, what are the main takeaways?

JSX
// Routing.js
import React, { useState, useMemo, createContext } from 'react'
/* Simple object which we use to set and
* identify the pages in our router
*/
export const pagesMapping = {
home: "home",
about: "about",
contact: "contact"
}

With the help of useState, we keep track of the current page we need to route to. This is stored in the variable page, which by default will be set to whatever is specified in the URL path. In addition to that, we have the function setPage with which we can dynamically change the page, such as in a navigation bar.

Next ⏭, we store these the aforementioned variable and function within an Object Literal in a variable called value. You will notice that we do so using the useMemo hook. This makes our app a bit more efficient in reacting to any changes in our state.

Lastly, we want to make our state value (i.e. page and setPage) accessible to our entire application. We do so using the Context API. When we create the context RoutingContext, we can pass a default value, though you may leave it blank if you chose to do so (unless you use TypeScript! 😆).

JSX
// Routing.js
export const RoutingContext = createContext({ page: pagesMapping.home })
export default function Router({ children }) {
/* Read the urlPath, e.g. '/about' or '/' */
let urlPath = window.location.pathname.slice(1).toLowerCase()
/* Set the default page to Home if not specified otherwise in the URL */
const [page, setPage] = useState(urlPath || pagesMapping.home)
const value = useMemo(
() => ({ page, setPage }),
[page, setPage]
)
return (
<RoutingContext.Provider value={value}>
{children}
</RoutingContext.Provider>
)
}

Our component Router will return this context, which will be made accessible to all its children through the props value. And how do we ensure it's truly accessible to all components in the application? We insert it at the very root of the app, of course! 😋 This can be done in the index.js file as seen below.

JSX
// index.js
import Router from './context/Routing'
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById('root')
)

All clear so far?

Meme about explaining React routing solution without React Router

Seriously though, leave any questions in the comment section 😊

Displaying the right pages

Alrighty then, we've go the routing logic set up. Now, what about showing the right pages depending on what the router wants? Easy peasy! For this we shall take a look at App.js.

To gain access to our RoutingContext we created earlier, we need to use useContext. From it, we can can destructure the variable page. Just to clarify, this is the same variable we passed into value along with the function setPage. Equipped with our omniscient power on the state of our app, we can then conditionally render relevant components, i.e. pages, depending on the value of page.

JSX
// App.js
import { useContext } from 'react'
import Home from './pages/Home'
import About from './pages/About'
import Contact from './pages/Contact'
import { pagesMapping, RoutingContext } from './context/Routing'
import './App.css'
function App() {
const { page } = useContext(RoutingContext)
return (
<>
{(pagesMapping.home === page) && <Home />}
{(pagesMapping.about === page) && <About />}
{(pagesMapping.contact === page) && <Contact />}
</>
)
}
export default App

That's all, really! Not much else to explain here. 😛

Manually changing the page

How do we go about changing the page manually, say by clicking on an element? To explain this, let's take a look Home.js as an example. Just as in the previous section, we need to gain access to our all mighty Context API with useContext. Instead of retrieving page like last time, we retrieve the function setPage instead.

The rest of the code should be quite familiar for you. We simply invoke the function with onClick and set it to the desired page using a property from pagesMapping.

JSX
// Home.js
import { useContext } from 'react'
import { RoutingContext, pagesMapping } from '../context/Routing'
import logo from '../logo.svg'
export default function Home() {
const { setPage } = useContext(RoutingContext)
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Welcome to the <code>Home</code> page.
</p>
<p className="App-link" onClick={() => setPage(pagesMapping.about)}>
About
</p>
</header>
</div>
)
}

Just the beginning

Yay! You've made it to the end of this article. 😁 Once you've understood the routing setup in Routing.js, the rest shouldn't be much of a challenge to understand. This custom example is by no means a complete solution. For example, I've not included a 404 page for an invalid URL path. Neither does the URL change when switching between pages. These are all things that can be included if you'd like to.

I hope with these basic building blocks you can do React routing without a library like React Router when the situation arises. 😉


WRITTEN BY

Code and stuff