useRef value is undefined on initial render
Asked Answered
C

4

13

I'm making a map with react and react-leaflet. React Leaflet has a class called FeatureGroup, which I want to access on the first render using the useRef hook from React and then accessing the value in a useEffect function. However on initial render the ref is undefined (markerRef.current returns null). Only after I make any sort of changes to the code, save and the React app reloads it gets the value

Can you tell me what I'm doing wrong and how I can make it so that markerRef.current is not null on the initial render?

Please find the abbreviated code for the component below:

import {useRef} from 'react';
import {FeatureGroup, //... } from 'react-leaflet';

const MapView = ({breweries}) => {
  const markerGroupRef = useRef(null);
  //...
  useEffect(() => {
    if(markerGroupRef.current) {
      //here I want to access a method called getBounds() which is in the markerGroupRef.current object 
      //but markerGroupRef.current has null value and so it doesn't execute
      //when making a save change and react app reloads it has the FeatureGroup class as value as expected
      console.log(markerGroupRef.current)
    }
  }, [markerGroupRef])
  //...
  return (
            <FeatureGroup ref={markerGroupRef}>
              {breweries && breweries.map(brewery => <Brewery key={breweries.indexOf(brewery)} brewery={brewery}/>)}
              {breweryStore && breweryStore.searchLocation &&  <LocationMarker markerPosition={{lat: breweryStore.searchLocation.lat, lng: breweryStore.searchLocation.lon}}/>}
            </FeatureGroup>
    );
  }
Creation answered 28/1, 2021 at 16:46 Comment(4)
how are you trying to use markerGroupRefConveyance
its always undefined on first render if you don't pass an initialValueKamilah
It's because the ref is updated after render time because the DOM element doesn't exist before render time. #22238820Bedel
@Mr.Robot thanks for the link. I'm not sure how to translate its solution to a functional component with hooks though. Also I updated my question to show you how I try to access it in a useEffect function.Creation
C
14

References create by useRef do not trigger component rerenders. So the useRef hook you created will never execute. Also since you initialized the ref to null, at the start of the first render, it will remain null. During the first render, the ref={markerGroupRef} on FeatureGroup will update it, but again it won't trigger another render. State must be modified to trigger any possible rerenders.

You might want to use a callback ref as indicated here

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

and justified here here

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

Cyclorama answered 5/8, 2021 at 2:33 Comment(0)
B
5

Your problem is that updating a ref doesn't make the component re-render, so useEffect only ever runs once and on that first render the ref is still null.

What you describe is the correct behaviour and you will need to wait for a re-render to do anything with the ref. If you force a re-render you will be able to use the ref.current property:

    const MapView = ({ breweries }) => {
        const markerGroupRef = useRef(null);
        const [refAquired, setRefAquired] = useState(false)
        //...
        useEffect(() => {
            setRefAquired(true)
        }, []);

        useEffect(()=> {
            console.log(markerGroupRef.current)
        }, [refAquired])

This is a crude demonstration and you should try to make the render cycles tie in to you other rendering and business logic.

Bedel answered 28/1, 2021 at 17:27 Comment(0)
R
1

I had the same issue; the <Map ref={mapRef ref wasn't defined when trying to use it during a useEffect [].

None of the suggestions above worked for me... but they did show me that when the ref is set, it doesn't cause a re-render.

What did work was finding the <Map onLoad={()=>setMapLoaded(true) onLoad prop.

With that prop, I was able to trigger my necessary useEffect and it works as expected.

Roberto answered 17/4 at 21:4 Comment(0)
T
0

As an alternative, you can force a component to render again immediately following its initial render via a useEffect hook, which ensures access to the element. I'm not sure if this is best practice, but honestly the performance implications are minimal.

const [shouldUpdate, setShouldUpdate] = useState(true)

// set shouldUpdate => true on initial render, triggering re-render
useEffect(() => {
  if (shouldUpdate) setShouldUpdate(false)
}, [shouldUpdate])
Thirst answered 5/8, 2021 at 2:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.