Navigate to # id in Layout component

Viewed 673

I'm using Gatbsy and I'm trying to create an accessibility link for the users (using screen readers) to be able to navigate to content (skipping the navigation).

My layout component (which is used in every page of the website) looks something like this:


const Layout = ({ children }) => {

{/* hidden for brevity */} 

return (

 <>
   <a href="#main-content">Skip to content</a> {/* <-reference line */}

   {/* hidden for brevity */} 

   <main id="main-content">
      {children}
   </main>


   {/* hidden for brevity */}

 </>

);

}

Current behaviour:

As it's shown in the code example above the link will be generated once for the page I'm "landing on", and it will not update for subsequent pages I navigate to: ex. landing on the 'blog' page my link will point to localhost:9000/blog#main-content and navigating to 'about' page will not make that section of the layout re-render (this is most likely normal behaviour).

I also tried using useRef, but got the referenced item being undefined during rendering.

What would be the way to complete this task (considering that the pages are server-side-rendered)? Is there a way I could do it without passing the full route path to use it inside the tag?

1 Answers

As I commented above, Gatsby's <Link> component extends from @reach/router (from React) doesn't allow navigating to any parameter. From the docs:

Neither <Link> nor navigate can be used for in-route navigation with a hash or query parameter. If you need this behavior, you should either use an anchor tag or import the @reach/router package—which Gatsby already depends upon—to make use of its navigate function.

If you use navigate, for example, navigate("/blog#main-content") it will redirect to /blog, omitting the parameter since it's not allowed.

In your case, the useRef approach won't directly since at the moment you are creating it, it's not still rendered so, you can use the useRef hook approach alongside with useEffect hook, to ensure that the DOM tree is loaded or using some manual trigger:

const Layout = ({ children }) => {
  const mainRef= useRef(null);


  const navigateToContent= () => {
    mainRef.current.scrollIntoView(); //manual trigger 
  }

 useEffect(()=>{
    mainRef.current.scrollIntoView();  //automatic trigger 
 }, [])
 
{/* hidden for brevity */} 

return (

 <>
   <div onClick={navigateToContent}>Skip to content</div> 

   {/* hidden for brevity */} 

   <main id="main-content" ref={mainRef}>
      {children}
   </main>


   {/* hidden for brevity */}

 </>

);

}

In the snippet above I've added two different approaches, choose the one that fits your requeriements. The key part is to set correctly the reference of the main tag, initially set as null to avoid rehydration issues when the routing changes.

The manual trigger, just call a function (navigateToContent) in your Skip to content element that uses the scrollIntoView() built-in function to scroll to the reference.

The automatic trigger, just uses the same idea but the function is triggered once the DOM tree is loaded (useEffect with empty deps, []).


The issue has been solved as following:

Actually changing this <Link to="/#main-content">Skip to content</Link> to this <Link to="#main-content">Skip to content</Link> (removing the /) works perfectly fine

Related