Home > Software engineering >  Why would useRef object be null even after attaching to a div element?
Why would useRef object be null even after attaching to a div element?

Time:02-04

Hey yall Im having a weird and annoying issue while trying to useRef on a div element. I have this working exactly as it is on another page but this doesnt seem to be doing what I want it to on this page.

Im trying to implement and endless scroll. The goal is to attach the ref (refetchTrigger) to a certain div on the page and have that trigger a fetch for the next page of data when scrolled into view. It seems to render the div correctly but refetchTrigger is not updated to be the div, it just remains null. Seems like a rerender needs to happen here but obviously changes to refs dont trigger a rerender. Ive been battling with this all morning and would greatly appreciate any suggestions. In the code snippet below, all console.log(refetchTrigger.current) are printing out null.

Its also worth noting that the refetch call is using useSWR hook to fetch data. when removing this the attaching of ref to div seemed to work correctly. also, when adding a button to fetch its fetching as expected. its just trying when trying to automatically trigger the fetch that Im seeing the issue.

Thanks for the help!

export const TrackGrid = () => {
  const [list, setList] = useState<Track[]>([]);
  const [page, setPage] = useState<number>(1);
  const refetchTrigger = useRef<HTMLDivElement | null>(null);
  const inViewport = useIntersection(refetchTrigger, "0px");

  const { tracks, error, isValidating } = useGetTracks(false, page, 20);

  useEffect(() => {
    if (inViewport) {
      setPage(page   1);
    }

    console.log("in viewport");
  }, [inViewport]);

  useEffect(() => {
    if (tracks) setList([...list, ...tracks]);
  }, [tracks]);

  const renderDiv = () => {
    console.log(refetchTrigger.current);
    const d = <div ref={refetchTrigger}>exists</div>;
    console.log(refetchTrigger.current);
    return d;
  };

  return (
    <>
      <div className="grid place-items-center grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
        {!!list.length && renderDiv()}

        {list.map((track: Track, i: number) => {
          console.log(refetchTrigger.current);
          return (
            <div ref={refetchTrigger} key={i}>
              <TrackGridItem track={track} />
            </div>
          );
        })}
      </div>
    </>
  );
};

Here is the code thats interacting with the ref

```export const useIntersection = (element: any, rootMargin: string) => {
  const [isVisible, setState] = useState<boolean>(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        setState(entry.isIntersecting);
      },
      { rootMargin }
    );

    element.current && observer.observe(element.current);

    return () => element.current && observer.unobserve(element.current);
  }, []);

  return isVisible;
};```

CodePudding user response:

The ref only gets populated after the render is complete, and the element has been created by react on the actual dom. Logging the ref out during the render will not work (unless there has been a previous render).

The fix is to put your code that needs to interact with the ref into a useEffect. That way by the time your code runs, the render is complete and the element is on the page.

useEffect(() => {
  console.log(refetchTrigger.current)
}, []);

CodePudding user response:

Essentially the ref will not be populated until after the whole render pass is finished. This is not obvious to many React programmers (and it usually doesn't matter) but the DOM is not actually committed until later on. When you call renderDiv() and pass the ref, on the first mount the element is not even rendered in the DOM at the stage that code executes. React effectively executes and renders the virtual DOM tree into the real DOM shortly after the render pass.

If you have code that is reliant on the DOM node existing, because you need to read something for whatever reason you need to run it an effect or (this is something you have to be careful with), run it in a ref callback.

CodePudding user response:

The fix for me was updating the useEffect to remove the dependency array. According to the new react docs, the recommended read or write example is in a useEffect without a dependency array (runs every time).

In my above example the ref is being used in the useIntersection hook. I removed the dependency array and it worked as expected

  • Related