You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
react-router-hash-link's components work perfectly, however I have a situation in which I need to navigate (using useNavigate from react-router v6) programmatically: the scroll feature is thus gone. It's actually pretty easy to wrap react-router-hash-link codebase into a hook, here is the working snippet:
import{useCallback,useState,useMemo}from'react';import{useNavigate}from"react-router";functionisInteractiveElement(element){constformTags=['BUTTON','INPUT','SELECT','TEXTAREA'];constlinkTags=['A','AREA'];return((formTags.includes(element.tagName)&&!element.hasAttribute('disabled'))||(linkTags.includes(element.tagName)&&element.hasAttribute('href')));}constuseScrollNavigate=(props={})=>{constnavigate=useNavigate();constobserverRef=useRef(null);constasyncTimerIdRef=useRef(null);constscrollFunction=useMemo(()=>{return(props.scroll||(el=>props.smooth
? el.scrollIntoView({behavior: 'smooth'})
: el.scrollIntoView()));},[props.scroll,props.smooth]);constreset=useCallback(()=>{if(observerRef&&observerRef.current!==null){observerRef.current.disconnect();observerRef.current=null;}if(asyncTimerIdRef&&asyncTimerIdRef.current!==null){window.clearTimeout(asyncTimerIdRef.current);asyncTimerIdRef.current=null;}},[]);constgetElAndScroll=useCallback((hashFragment)=>{letelement=null;if(hashFragment==='#'){// use document.body instead of document.documentElement because of a bug in smoothscroll-polyfill in safari// see https://github.com/iamdustan/smoothscroll/issues/138// while smoothscroll-polyfill is not included, it is the recommended way to implement smoothscroll// in browsers that don't natively support el.scrollIntoView({ behavior: 'smooth' })element=document.body;}else{// check for element with matching id before assume '#top' is the top of the document// see https://html.spec.whatwg.org/multipage/browsing-the-web.html#target-elementconstid=hashFragment.replace('#','');element=document.getElementById(id);if(element===null&&hashFragment==='#top'){// see above comment for why document.body instead of document.documentElementelement=document.body;}}if(element!==null){scrollFunction(element);// update focus to where the page is scrolled to// unfortunately this doesn't work in safari (desktop and iOS) when blur() is calledletoriginalTabIndex=element.getAttribute('tabindex');if(originalTabIndex===null&&!isInteractiveElement(element)){element.setAttribute('tabindex',-1);}element.focus({preventScroll: true});if(originalTabIndex===null&&!isInteractiveElement(element)){// for some reason calling blur() in safari resets the focus region to where it was previously,// if blur() is not called it works in safari, but then are stuck with default focus styles// on an element that otherwise might never had focus styles applied, so not an optionelement.blur();element.removeAttribute('tabindex');}reset();returntrue;}returnfalse;},[reset,scrollFunction]);consthashLinkScroll=useCallback((hashFragment)=>{// Push onto callback queue so it runs after the DOM is updatedwindow.setTimeout(()=>{if(getElAndScroll(hashFragment)===false){if(observerRef.current===null){observerRef.current=newMutationObserver(()=>getElAndScroll(hashFragment));}observerRef.current.observe(document,{attributes: true,childList: true,subtree: true,});// if the element doesn't show up in specified timeout or 10 seconds, stop checkingconstasyncTimerId=window.setTimeout(()=>{reset();},10000);asyncTimerIdRef.current=asyncTimerId;}},0);},[getElAndScroll,reset]);constscrollNavigate=useCallback((path,options)=>{reset();constmatch=path.match(/^.*?(#.*)$/);consthash=match ? match[1] : null;navigate(path,options);if(hash){hashLinkScroll(hash);}},[hashLinkScroll,navigate,reset]);returnscrollNavigate;};exportdefaultuseScrollNavigate;
Basically, everything remains the same, except some variables are passed to functions. The function returned by the hook useScrollNavigate is used exactly in the same way as navigate returned by useNavigate. useScrollNavigate accepts the same extra options of the HashLink component: {smooth: Boolean, scroll: Function}. The components can then directly use this hook to reuse the business logic. Didn't make a PR as I don't have much time and not quite sure if these changes make sense at all.
The text was updated successfully, but these errors were encountered:
I'd like to suggest another solution that looks much cleaner to me: a component that watch for url hash change and handle scroll on render, rather that on click. It doesn't require that package and works with useNavigate out-of-the-box.
react-router-hash-link
's components work perfectly, however I have a situation in which I need tonavigate
(usinguseNavigate
fromreact-router
v6) programmatically: the scroll feature is thus gone. It's actually pretty easy to wrapreact-router-hash-link
codebase into a hook, here is the working snippet:Basically, everything remains the same, except some variables are passed to functions. The function returned by the hook
useScrollNavigate
is used exactly in the same way asnavigate
returned byuseNavigate
.useScrollNavigate
accepts the same extra options of theHashLink
component:{smooth: Boolean, scroll: Function}
. The components can then directly use this hook to reuse the business logic. Didn't make a PR as I don't have much time and not quite sure if these changes make sense at all.The text was updated successfully, but these errors were encountered: