How to Use React Bootstrap Navbar's collapseOnSelect Property With Custom Links

React Bootstrap provides a convenient, simple and responsive design system, but with a few rough edges. This blog shows you how to get the collapseOnSelect prop to work with both React Router Links and Hash Links.

How to Use React Bootstrap Navbar's collapseOnSelect Property With Custom Links
Photo by Minh Tran / Unsplash
Rover

React Bootstrap provides a convenient, simple and responsive design system, with reusable components to add functionality, style and structure to your website.   There are a couple areas where things can get a little complicated, like getting Navbar and its CollapseOnSelect property to work as intended.  And looking around the internet, it appears I'm not the only one that's encountered some difficulty.  The good news is that I recently updated Cork Hounds from React Bootstrap v0.33.1 to v1.6.4, and I was finally able to get CollapseOnSelect to work with both React Router Links and Hash Links. The solution is relatively simple, but this is one case where having a working example can really help.  Therefore, I'll start by stepping through the highlights and wrap up with an overview of a fully functional solution in codesandbox.io.  

Overview

So, what is CollapseOnSelect, and why do we need a blog dedicated to it? CollapseOnSelect is a (finicky) setting for the Navbar component that comes into play in two primary circumstances:

  • When you use dropdown menus that you want to close after the user clicks on a NavDropdown.Item, or
  • When you use the Navbar.Collapse component which will automatically convert your Navbar into a hamburger menu when your app is rendered on a small screen (0r resized past a threshold).  You want the menu to collapse when the user makes a selection. Note, the expand prop allows for collapsing the Navbar at various breakpoints.  

When using the Navbar component, you'll typically need to accommodate (1) internal routes, (2) anchor tags/refs, or (3) external sites.  Since most React sites with internal routes use React Router, we'll be working with Links.  For anchor refs, React Router Link provides the innerRef prop.  The refs will need to be accessible to your Navbar component, and there are lots of ways to do that.  You could also use anchor tags (or Hash Links), e.g. id="first".  React Router doesn't support anchor tags, but there is a libary called React Router Hash Link which provides that functionality.  To align with ReactJS best practices, it's probably important to say that referencing elements in the DOM directly is an anti-pattern, and you should use refs instead.  But there are some circumstances where it's not convenient to use refs, and you just need to get something working.  The third and final use case of linking to external URLs works out of the box with the Nav.Link href prop unless you need special handling.

The end goal when using the CollapseOnSelect prop is to collapse the menu as intended when the user clicks on any of these links.  In addition, you want to send the user to the precise location they expect (e.g. at the top of an anchor tag/ref, or on the appropriate internal page).  There's a little added complexity here when we throw in the fact that Navbar.Collapse changes the height of the header when the user interacts with the menu.  We'll want to solve for this so that it doesn't interfere/offset the page when navigating to an anchor tag/ref.

Since using React Router Hash Link is emblematic of 'custom' handling, I'll step through an example that demonstrates how to make Links and Hash Links work with Navbar and the CollapseOnSelect prop.

Assumptions and Getting Started

For this blog, I assume you have a reasonably good grasp of JavaScript, package management (npm or yarn), and ReactJS, to include React Router (react-router-dom), and are familiar with React Bootstrap.  I've tried to mitigate these barriers by providing a fully working example generated with create-react-app in codesandbox.io that you can use to jumpstart your project with React Bootstrap Navbar.  Our project uses the following dependencies:

{"bootstrap": "4.6.1",    "react": "16.14.0",    "react-bootstrap": "1.6.4",    "react-dom": "17.0.2",    "react-router-dom": "5.3.0",    "react-router-hash-link": "2.4.3",    "react-scripts": "4.0.0"}

Code

Let's start by looking at a condensed example of a Navbar using CollapseOnSelect with Nav.Links and NavDropdown.Items, with standard React Router Links for pages and a custom Link implementation for anchor tags (a.k.a. hash links).  

There are a couple things to notice in this example.  First, we are showing two Nav.Links and two NavDropdown.Items; each of these illustrates using a standard React Router Link and a Custom Link, which we'll elaborate on more momentarily. The React Router Links will work out of the box; no special handling / considerations are needed.  However, for a custom Hash Link, we need to pass the header height to the MyHashLink component so it knows how far to offset the scroll position.

To do this, let’s look at a class-based approach for loading the Navbar, accurately calculating the height of the header and storing the value in component state, which we’ll callthis.state.headerHeight. We’ll do this using two steps. First, we set the headerHeight when the component mounts by calling the updateHeaderHeight() method from the `componentDidMount()` lifecycle method.  Notice that in the updateHeaderHeight() method, we use this.divElement.clientHeight to get the height of the ref we set on the div container that wraps the Navbar in the example provided above.

Second, because images can load slowly, we want to update the headerHeight when our header image loads. You’ll notice that in the snippet below (repasted from the Navbar section up above for convenience) we call this.updateHeaderHeight() to do this.

Now that we have an accurate header size, we can look at our custom MyHashLink component.  The functional component shown below receives the properties associated with the Nav.Link and NavDropdown.Item, to include the data-height, to, and other important properties.  

In our custom example, we want to scroll to an anchor tag.  We will use the React Router Hash Link library to accomplish this.  This library exports two components:  HashLink and NavHashLink.  We will use NavHashLink because it correctly applies the active className out of the box when the link is clicked.  In order to trigger the Navbar to collapse when a NavHashLink is clicked, we set its onClick property: onClick={props.onClick}.  

Next, we use the scroll property to pass the element to our scrollWithOffset function to customize the scrolling behavior.  Here,  we use the incoming props["data-height"] for the yOffset when scrolling to the selected anchor tag.

That's all the special handling that we need to do for collapseOnSelect to work properly with React Router Links and a custom Hash Link implementation.

Working Example

Let's put all this together.  There's nothing quite like having a fully working example to illustrate how everything comes together.  This project was created in codesandbox.io using create-react-app (CRA).  The entry point for this app is the src/index.js file where we introduce the Header class described above, and also setup React Router .  

The base route "/" points to the App component, and there are some other example routes pointing to a PageComponent.   Inside the App component, we introduce the HashComponent to illustrate the use of multiple anchor tags throughout a page.   The Header and MyHashLink components work together to provide navigation. We map React Router Links to the Routes specified in index.js which point to a PageComponent while our anchor tags point to a HashComponent.

When clicking on menu items, the menu will collapse as intended, and navigate to the appropriate anchor or page.  Feel free to explore this and make use of it as you see fit.

Conclusion

Hopefully you found this blog helpful.  Leave a comment if you found this useful or need any assistance!