React Router Hash Link
React Router Hash Link provides essential hash fragment scrolling functionality for applications built with React Router v4/5/6. Addressing a long-standing limitation in native React Router's `<Link>` component, this library ensures that navigation to URLs containing hash fragments (e.g., `/path#section-id`) correctly scrolls the viewport to the corresponding HTML element. It is particularly robust as it supports scrolling to elements that might be rendered asynchronously, which is common in data-driven React applications. The current stable version is 2.4.3, with a release cadence that has seen several minor updates, indicating active maintenance. Key differentiators include the ability to specify `smooth` scrolling behavior, a flexible `scroll` prop for custom scrolling logic (e.g., with offsets), and an `elementId` prop offering an alternative to hash fragments. It requires React Router's `BrowserRouter` for proper operation and ships with both `<HashLink>` and `<NavHashLink>` components, mirroring `react-router-dom`'s `Link` and `NavLink`.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'scrollIntoView')
cause The element corresponding to the hash fragment or `elementId` prop could not be found in the DOM within the default 10-second timeout, or it never existed.fixEnsure the target element has a matching `id` attribute. For asynchronously loaded content, consider increasing the `timeout` prop on `HashLink` if content takes longer to render. -
HashLink is not scrolling to the correct element or not scrolling at all.
cause This typically happens if you are not using `BrowserRouter` or if the `id` of the target element does not exactly match the hash fragment in the `to` prop.fixVerify that your application uses `BrowserRouter` and that the `id` attribute of your target element precisely matches the hash fragment (e.g., `<HashLink to="/#my-id">` targets `<div id="my-id">`). -
NavHashLink's `activeClassName` or `activeStyle` is not being applied when the hash fragment matches.
cause Unlike `NavLink`, `NavHashLink` requires both the path *and* the hash fragment to match for the link to be considered active by default.fixEnsure both the path and the hash fragment in the `to` prop of `NavHashLink` exactly match the current URL for the active styles to apply.
Warnings
- breaking Version 2.0.0 introduced breaking changes related to `React.forwardRef` implementation and simplified the custom link API. Projects upgrading from v1.x to v2.x may need to update custom link components.
- gotcha This library requires React Router's `BrowserRouter` to function correctly. It will not work as expected with `HashRouter` or other router types.
- gotcha The `smooth` prop for smooth scrolling relies on the native `Element.scrollIntoView({ behavior: 'smooth' })` method. This feature is not supported in all browsers (e.g., older IE versions).
- gotcha Hash fragments `#` or `#top` are special and trigger scrolling to the top of the current page, aligning with HTML spec behavior. Using these as IDs for specific elements will not work as intended for section scrolling.
Install
-
npm install react-router-hash-link -
yarn add react-router-hash-link -
pnpm add react-router-hash-link
Imports
- HashLink
const { HashLink } = require('react-router-hash-link');import { HashLink } from 'react-router-hash-link'; - NavHashLink
import NavHashLink from 'react-router-hash-link/NavHashLink';
import { NavHashLink } from 'react-router-hash-link';
Quickstart
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { HashLink } from 'react-router-hash-link';
import React from 'react';
import ReactDOM from 'react-dom/client';
function App() {
return (
<BrowserRouter>
<nav style={{ padding: '20px', background: '#f0f0f0', position: 'sticky', top: 0, zIndex: 100 }}>
<HashLink to="/#section1" smooth>Go to Section 1 (Smooth)</HashLink> | {' '}
<HashLink to="/#section2">Go to Section 2 (Instant)</HashLink> | {' '}
<HashLink to="/#top">Go to Top of Page</HashLink>
</nav>
<div id="top" style={{ height: '100vh', background: '#eef', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h1>Welcome to the Top</h1>
</div>
<div id="section1" style={{ height: '100vh', background: '#ccf', paddingTop: '80px' }}>
<h2 style={{ marginLeft: '20px' }}>Section 1</h2>
<p style={{ marginLeft: '20px' }}>This is the first section. Notice the smooth scroll effect.</p>
</div>
<div id="section2" style={{ height: '100vh', background: '#aac', paddingTop: '80px' }}>
<h2 style={{ marginLeft: '20px' }}>Section 2</h2>
<p style={{ marginLeft: '20px' }}>This is the second section. This one scrolls instantly.</p>
</div>
</BrowserRouter>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);