Building a Navbar with React-Router, React-Bootstrap, and TypeScript

I’m building a React SPA and using react-router-dom for routing, and react-bootstrap as a component library. For a simple header, I had several goals:

  1. Active nav links are styled (you can tell which one you’re on, and hovering the link to the page you’re on does not show a pointer or hover style; but links to other pages do)
  2. Clicking the active link (or the Navbar.Brand component, when on the home page) should not reload the page you’re already on
  3. On mobile, clicking a nav menu item should collapse on click
  4. Use TypeScript

These three goals seemed very straightforward and not contradictory; but I had a hard time making them play nicely together.

The Problem

To detect the active links, the NavLink component of react-router-dom has an isActive property you can reference in an inline style, for example:

style={({ isActive }) => ({ cursor: isActive ? 'default' : 'pointer' })}

However, to get the react-bootstrap Navbar menu to collapseOnSelect, you have to use the react-bootstrap Nav.Link component, not NavLink.

You also can’t nest a Nav.Link within a NavLink; nesting <a> tags breaks the HTML standards.

I tried many iterations; getting one to work using “as” to render a different component than I specified:

<Nav.Link as={NavLink} eventKey="1" style={({ isActive }) => ({ cursor: isActive ? 'default' : 'pointer' })} to="/">

…until I tried to add TypeScript. Then it was just barking about the conflicting properties between those two definitions.

The Solution

Enter another library:

import { LinkContainer } from 'react-router-bootstrap';

Then the code was blissfully simple, working properly, and conforming to TypeScript! I had to add in the forced active={false} since both libraries apply an “active” property, which leaves multiple links appearing “active” after navigating. Best to disable the Nav.Link ones and just let LinkContainer do the work.

<Nav>
    <LinkContainer to="/">
        <Nav.Link active={false}>Home</Nav.Link>
    </LinkContainer>
    <LinkContainer to="/about">
        <Nav.Link active={false}>About</Nav.Link>
    </LinkContainer>
</Nav>

Fixing 404 error for a React SPA on a subdomain

The problem:

I have WordPress installed on my website’s root domain; I wanted to deploy an unrelated React SPA to a subdomain on the same site. The root of the SPA loaded fine, but if I tried to access any routes deeper than the root, I was being served the 404 from my wordpress page.

What I already did:

I set up the application to be served from a subdirectory according to the React Router guidelines:

  1. Updated package.json to have a line for:
    "homepage": "http://subdomain.eknighmusic.com/myApp"
  2. Updated the Router component to have a basename:
    <Router basename={'/myApp'}>
  3. Confirmed any links within my app are using the react-router-dom Link component (or useNavigate) instead of a direct “href”
  4. Updated my image source to: img src={`${process.env.PUBLIC_URL}/icon.png`}

This had everything working from localhost, serving from localhost:3000/myApp and looking good; but I was still having this 404 error after deploying a build and navigating to a route.

The solution:

The .htaccess file at my primary domain’s root was taking over and redirecting any unknown (to the server) routes to my 404 page.

I needed to add a new .htaccess file (filename: .htaccess) in my application’s directory on the server (I used cPanel to do this, but you could use FTP or anything you normally use to move files to your server) with the following contents:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.html [L]

Now I can access http://subdomain.eknighmusic.com/myApp/route without getting a 404 error 🙌