Esta es una versión (muy) simplificada de mi componente:
export const DynamicComponent: FC<DynamicComponentProps> = (props) => { const ref = useRef<HTMLElement>(null); const [isSticked, setIsSticked] = useState(false); const parentSticked = useContext(StickyContext); const [overridedStyles, setOverridedStyles] = useState(props.styles ?? {}); const [overridedArgs, setOverridedArgs] = useState(props.args ?? {}); const { config } = useContext(GlobalContext); const data = useContext(DataContext); const [state, setState] = useContext(StateContext); const mountComponent = useMemo(() => { if (typeof props.mount === "undefined") return true; if (typeof props.mount === "boolean") return props.mount; if (typeof props.mount === "number") return props.mount === 1; if (typeof props.mount === "string") { let mount = stateParser.parse(props.mount, state) as unknown; return mount == true; } return false; }, [state, props.mount]); useLayoutEffect(() => { setTimeout(() => { const anchorHash = location.hash; if ( anchorHash && document && document.querySelector(anchorHash) && !document .querySelector(anchorHash) ?.classList.contains("already-scrolled") ) { document?.querySelector(anchorHash)?.scrollIntoView(); document?.querySelector(anchorHash)?.classList.add("already-scrolled"); } }, 50); }, []); let output = mountComponent ? ( <StickyContext.Provider value={{ sticked: isSticked }}> <StyledDynamicComponent {...props} ref={ref} isSticked={applyStickedStyles} args={overridedArgs} styles={overridedStyles} /> </StickyContext.Provider> ) : null; return output; }; El código dentro de useLayoutEffect no se ejecutará correctamente sin setTimeout porque el componente no está completamente renderizado y document?.querySelector(anchorHash) aún no existe.
Probé con window.onload pero el código que contiene nunca se ejecutará.
¿Hay alguna manera de evitar el uso de ese horrendo setTimeout?
También tenga en cuenta que el ancla o el elemento anclado son opcionales, por lo que no sé cómo usar las referencias de devolución de llamada
No use document.querySelector y no verifique los nombres de las clases, si puede usar estados para ello.
No necesita setTimeout en absoluto, ya que useEffect y useEffectLayout son más o menos lo mismo que componentDidMount :
Si está migrando código de un componente de clase, tenga en cuenta que useLayoutEffect se activa en la misma fase que componentDidMount y componentDidUpdate. Sin embargo, recomendamos comenzar con useEffect primero y solo probar useLayoutEffect si eso causa un problema. useLayoutEffect-Docs
Traté de reducir su muestra un poco más y la hice depurable en la caja de códigos (con suerte manteniendo su lógica intacta).
Pero la parte más importante sería la siguiente:
const ref = useRef(); useEffect(() => { if (!ref.current || !document) { return; } // check if a hash is provided // possible todo: is the current element id the same as the provided location hash id if(!location.hash) { return true; } // check if we've scrolled already if(scrolled) { return; } ref.current.scrollIntoView(); console.log("scroll to view", ref); setScrolled(true); }, [ref, location, scrolled]); Luego, su componente se representará cada vez que la ref , la location o las variables scrolled hayan cambiado, pero solo debe desplazarse a la vista, si no lo ha hecho antes.