Search code examples
reactjsreact-map-gl

Measure width of component without re-loading the page


I am using this hook to measure the size of a div component (https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node):

function useClientRect() {
  const [rect, setRect] = React.useState(null);
  const ref = React.useCallback(node => {
    if (node !== null) {
      setRect(node.getBoundingClientRect());
    }
  }, []);
  return [rect, ref];
}

This is my component, simplified. I'm using it along with React Map GL, as there is no way to set the width of the viewport in terms of %. I couldn't find other way to do it... getBoundsForPoints is a function to obtain the limits of the viewport of a map, given an array of coordinates and the width of the map in pixels.

function Mapas(props){  
    const [rect,ref] = useClientRect() 
    useEffect(()=>{
      const coordinates = [some coordinates]
      let new_viewport
      if (rect!==null){ //Here is when sometimes I get rect===null 
        new_viewport = getBoundsForPoints(coordinates, rect.width) 
      } else {
        new_viewport = getBoundsForPoints(coordinates, 500)
      }
      setViewport(new_viewport) 
    },[rect]) 

    return (
        <MDBContainer>
            <MDBRow>
              <MDBCol md='12' xl='8'>
                 <div ref={ref}> {/*Elemento referenciado */}
                  <ReactMapGL
                    {...viewport}
                    mapboxApiAccessToken='xxx'}
                    mapStyle='xxx'
                    onViewportChange={nextViewport => setViewport({...nextViewport})}
                    >
                  </ReactMapGL>
                </div>
              </MDBCol>
              <MDBCol xl='4' md='12'>
                 Other stuff
              </MDBCol>
            </MDBRow>       
      </MDBContainer>        
    )
}

export default Mapas

When the page is loaded it's working fine; however, when I get to the page from other component, without re-rendering, rect is null. How can I avoid this, thank you!


Solution

  • I finally had to use the hook useLayoutEffect, instead of useEffect. The hook (https://es.reactjs.org/docs/hooks-reference.html#uselayouteffect) is similar to useEffect but is run after all components are built, therefore the reference is now correctly defined and can measure the dimensions of the component. The final simplified code is the following:

    function useClientRect() {
      const [rect, setRect] = React.useState(null);
      const ref = React.useCallback(node => {
        console.log('Node')
        console.log(node)
        if (node !== null) {
          setRect(node.getBoundingClientRect());
        }
      }, []);
      return [rect, ref];
    }
    
    function Mapas(props){  
        const [rect,ref] = useClientRect() 
    
        useLayoutEffect(()=>{
          const coordinates = [some coordinates]
          let new_viewport
          if (rect!==null){ //Here is when sometimes I get rect===null 
            new_viewport = getBoundsForPoints(coordinates, rect.width) 
          } else {
            new_viewport = getBoundsForPoints(coordinates, 500)
          }
          setViewport(new_viewport) 
        },[rect]) 
    
        return (
            <MDBContainer>
                <MDBRow>
                  <MDBCol md='12' xl='8'>
                     <div ref={ref}> {/*Elemento referenciado */}
                      <ReactMapGL
                        {...viewport}
                        mapboxApiAccessToken='xxx'}
                        mapStyle='xxx'
                        onViewportChange={nextViewport => setViewport({...nextViewport})}
                        >
                      </ReactMapGL>
                    </div>
                  </MDBCol>
                  <MDBCol xl='4' md='12'>
                     Other stuff
                  </MDBCol>
                </MDBRow>       
          </MDBContainer>        
        )
    }
    
    export default Mapas