1

I've a navigation bar that scrolls to the page's sections using fragments, e.g:

// nav
<a href="#section_dev">{'Development'}</a>
// somewhere else in the page
<a id="section_dev">{'Development'}</a>

How do I apply a style to the navigation link while active (user is in http://myurl/#section_dev)?

I thought of something like this:

<Link href="#section_dev">
    <a
    className={router.fragment == '#section_dev' ? styles.active : ''}
    >
    {'Development'}
    </a>
</Link>

But the problem is that this functionality appears to be server side, so the router doesn't know the current fragment.

juliomalves
  • 42,130
  • 20
  • 150
  • 146
User
  • 31,811
  • 40
  • 131
  • 232

1 Answers1

1

The current path's fragment is only known on the client-side. This means you have to handle it on the client-side only.

You can start by creating a state variable that you'll use to track the fragment/hash value on the client. Then use an useEffect to set the variable value to the current path's fragment.

const [routeHash, setRouteHash] = useState();

useEffect(() => {
    setRouteHash(window.location.hash);
}, []);

// Further down in your JSX
<Link href="#section_dev">
    <a className={routeHash == '#section_dev' ? styles.active : ''}>
        Development
    </a>
</Link>

The useEffect is needed to set the initial value because window is not defined when the page gets pre-rendered on the server by Next.js. Trying to set the initial state in useState() will either cause runtime errors on the server, or hydration mismatches on the client.

The above will handle initial page loads that contain the fragment in the URL. If you also need to handle client-side navigations that include the fragment you have to add additional logic for it.

Using next/router, you can listen for changes in the URL fragment during client-side transitions, then update the existing routeHash variable.

const router = useRouter();

useEffect(() => {
    const onHashChangeStart = () => {
        setRouteHash(window.location.hash);
    };

    router.events.on('hashChangeComplete', onHashChangeStart);

    return () => {
        router.events.off('hashChangeComplete', onHashChangeStart);
    };
}, [router.events]);

Here's a codesandbox link showcasing the above solution: https://codesandbox.io/s/brave-frog-uxb515.

juliomalves
  • 42,130
  • 20
  • 150
  • 146
  • Thank you, that works perfectly! Except for one tiny issue: when there's an active link (with hash) and you enter a new hash in the url and press enter, it's not updated. For some reason, you have to press enter again, or refresh (Chrome, at least). But I'm leaving it like this because I don't think anyone will notice or be affected. – User Oct 17 '22 at 09:04
  • P.S. I added logs on both effects and on the first described update, none of the effects are called. On the second update, they're called. – User Oct 17 '22 at 09:10
  • @User So you're saying you have `/#section_dev` on the address bar and the link is active, but then change the URL manually to `/#other_hash` and press `Enter` to refresh the page, the link does not update for you (i.e. becomes inactive)? It does work for me on the sandbox link. – juliomalves Oct 17 '22 at 10:25
  • Yes, and confirm that it works correctly on the sandbox. I copied the exact code to a regular React project and there it has the described issue. But only on Chrome and Firefox (on Firefox it never updates). It seems related with the browser's URL suggestions, Safari doesn't show any suggestions, but Chrome shows them the first time and Firefox always. When clicking a suggestion (or pressing enter, which seems equivalent) the page doesn't reload. – User Oct 17 '22 at 13:43
  • @User _"I copied the exact code to a regular React project"_ - Do you mean a non-Next.js project? I assumed you were using Next.js as you tagged your question with [tag:next.js]. My solution is for Next.js specifically, it won't work with vanilla React as it depends on `next/router`. – juliomalves Oct 17 '22 at 17:45
  • Sorry, I meant a next.js project. That was just a writing error here. – User Oct 18 '22 at 08:21