useRef returns undefined after clicking the react-router NavLink

Viewed 54

I'm referencing an element in a hook that measures the elements dimensions. When I first load the page. The element reference number logs undefined, but then defines itself.

The dimensions are used to position an absolutely positioned element into the center. When the page first loads, the element is centered. As the window size is changed, the element remains centered.

However, when I click a NavLink from react-dom-router and click back to the home page where the element reference belongs, the reference logs as undefined without later defining it self. The element is not centered until I resize the page.

My setup is:

  • React v18.2.0
  • react-dom-router v6.3.0
  • styled-components v5.3.5

My intuition tells me that it's something to do with the useLayoutEffect dependency array..

But any suggestions and help on how to fix this bug would be greatly appreciated.

enter image description here

import React, { useRef } from "react";
import { useWindowDimensions } from "../services/hooks/useWindowDimensions";
import { useGetElementDimensions } from "../services/hooks/useElementDimensions";
import {
  HeroImage,
  HomeContainer,
  TextContainer,
  TextContainerOverlay,
  TextDescription,
} from "../components/StyledElements/HomeStyles.js";

const Home = () => {
  const textRef = useRef();
  const { width: windowWidth, height: windowHeight } = useWindowDimensions();
  const { width:textWidth, height: textHeight } = useGetElementDimensions(textRef)

  console.log(textRef);

  return (
    <HomeContainer height={`${windowHeight - 85}px`}>
      <HeroImage
        src="https://images.unsplash.com/photo-1587574293340-e0011c4e8ecf?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1932&q=80"
        alt="landingImage"
      />
      <TextContainer
        ref={textRef}
        left={`${(windowWidth - textWidth) / 2}px`}
        top={`${((windowHeight - textHeight) / 2) - 42}px`}
      >
        <TextDescription>
          Welcome to Pastasauce, a restaurant of italian cuisine focusing
          entirely on pasta. On the food selection screen you will be able to
          add what you want from the menu and it will be ready for you when you
          arrive at the restaurant.
        </TextDescription>
        <TextContainerOverlay></TextContainerOverlay>
      </TextContainer>
    </HomeContainer>
  );
};

export default Home;

The following is the custom hook, that I'm using for getting the elements dimensions.

import { useCallback, useLayoutEffect, useState } from "react";

export const useGetElementDimensions = (ref) => {

  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);

  const handleResize = useCallback(() => {
    setWidth(ref.current.offsetWidth)
    setHeight(ref.current.offsetHeight)
  }, [ref]);

  useLayoutEffect(() => {
    window.addEventListener('load', handleResize);
    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('load', handleResize)
      window.removeEventListener('resize', handleResize)
    }
  }, [ref, handleResize])

  return {width, height}
} 

1 Answers

It is not a bug.

It is undefined because you are assigning the reference of a dom element thats in the same component, which means it has to be mounted first to get a value.

https://reactjs.org/docs/refs-and-the-dom.html

    const Home = () => {
      const textRef = useRef(); // undefined
...
      const { width:textWidth, height: textHeight } = useGetElementDimensions(textRef) // still undefined
    
      console.log(textRef); // says undefined
    
      return (
...       <TextContainer
            ref={textRef} // gets value AFTER mount
            left={`${(windowWidth - textWidth) / 2}px`}
            top={`${((windowHeight - textHeight) / 2) - 42}px`}
          >
...
      );

EDIT:

Possible solution for your case after wrapping your function inside useLayoutEffect: Typescript playground link

export const useUpdateElementDimensions = (ref, onResize) => {
  useLayoutEffect(() => {    
    const resize = () => {
      onResize(ref.current.offsetWidth, ref.current.offsetHeight);
    }

    window.addEventListener('load', resize);
    window.addEventListener('resize', resize)

    return () => {
      window.removeEventListener('load', resize)
      window.removeEventListener('resize', resize)
    }
  }, [ref, onResize]);
} 

const Home = () => {
  const textRef = useRef();
  const { width: windowWidth, height: windowHeight } = useWindowDimensions();
  
  const onResize = (w, h) => {
    textRef.current.left = windowWidth - w / 2;
    textRef.current.top = windowHeight - h / 2 - 42;
  }

  useUpdateElementDimensions(textRef,onResize)
Related