React forwardRef and callback ref, ref becomes undefined

468 views Asked by At

This is not a Mapbox related issue, and you don’t need any map or related stuff knowledge to help. My issue is related to React refs.

I’m using react-map-gl (Mapbox for React) with @mapbox/mapbox-gl-draw that allow user to draw geometries on the map. This app has 2 components: DrawControl that exposes draw features and AllPastures that renders the map and manage create/update/delete geometries drawed.

When user draws a polygon it fires the DrawControl method onCreate that executes onCreateOrUpdate. I’m passing the Draw by forwardRef from DrawControl to AllPastures.

This is a DrawControl component:

export const DrawControl = React.forwardRef<MapboxDraw, DrawControlProps>(
  (props: DrawControlProps, ref) => {
    const drawRef: MapboxDraw = useControl<MapboxDraw>(
      () => new MapboxDraw(props),
      ({ map }) => {
        map.on('draw.create', props.onCreate);
        map.on('draw.update', props.onUpdate);
        map.on('draw.delete', props.onDelete);
        map.on('draw.modechange', props.onModeChange);
      },
      ({ map }) => {
        map.off('draw.create', props.onCreate);
        map.off('draw.update', props.onUpdate);
        map.off('draw.delete', props.onDelete);
        map.off('draw.modechange', props.onModeChange);
      },
      {
        position: props.position,
      }
    );

    // exposing drawRef outside the component by Ref
    React.useImperativeHandle(ref, () => drawRef, [drawRef]);

    return null;
  }

and this is part of AllPastures component:

export const AllPastures = () => {
  ...
  const [drawRef, setDrawRef] = React.useState<MapboxDraw>();

  // here I’m using callback ref to react to drawRef changes
  const onDrawRef = React.useCallback((ref: MapboxDraw | null) => {
    if (ref) {
      setDrawRef(ref);
    }
  }, []);

  React.useEffect(() => {
    // it's ok here, drawRef is not undefined
    console.log('useEffect drawRef when app is loading', drawRef);
  }, [map, drawRef]);

  const onCreateOrUpdate = (e: { features: Feature[] }) => {
    // Why drawRef is undefined here????????????????????
    console.log('drawRef under onCreateOrUpdate method', drawRef);
  };

  ...

  return (
      <DrawControl
        ref={onDrawRef}
        position="top-right"
        displayControlsDefault={false}
        controls={{
          polygon: true,
          trash: true,
        }}
        defaultMode="simple_select"
        onCreate={onCreateOrUpdate}
        onUpdate={onCreateOrUpdate}
      />
  );
};

My issue is, discover why when the the method onCreateOrUpdate the drawRef is undefined?


Here is a related sandbox simulating the issue: https://stackblitz.com/edit/vitejs-vite-bvutvb?file=src%2FAllPastures.tsx

Just draw any polygon on the map and check the console.log drawRef under onCreateOrUpdate method is undefined.

Please, after make changes in the code, do a F5 to complete refresh the page and test it again.

2

There are 2 answers

0
Joel On BEST ANSWER

For someone else is looking for a solution, I found a way forwarding the drawRef outside the DrawControl using forwardRef, useImperativeHandle and attaching that ref to my <DrawControl>.

The working example bellow, and a complete solution on: https://stackblitz.com/edit/vitejs-vite-bvutvb?file=src%2FDraw.tsx

export const Draw = () => {
  const { current: map } = useMap();
  const drawRef = React.useRef<MapboxDraw>(null);

  if (!map) {
    return null;
  }

  React.useEffect(() => {
    // it's ok here, drawRef is not undefined
    console.log('useEffect drawRef when app is loading', drawRef);
  }, [drawRef]);

  const onCreateOrUpdate = React.useCallback(
    (e: { features: Feature[] }) => {
      // Here drawRef would not be undefined
      console.log('drawRef under onCreateOrUpdate method', drawRef);
    },
    [drawRef]
  );

  return (
    <DrawControl
      ref={drawRef}
      position="top-right"
      displayControlsDefault={false}
      controls={{
        polygon: true,
        trash: true,
      }}
      defaultMode="simple_select"
      modes={{ ...MapboxDraw.modes, draw_rectangle: DrawRectangle }}
      onCreate={onCreateOrUpdate}
      onUpdate={onCreateOrUpdate}
    />
  );
};
4
ClusterH On
const onCreateOrUpdate = React.useCallback((e: { features: Feature[] }) => {
  // Here drawRef would not be undefined
  console.log('drawRef under onCreateOrUpdate method', drawRef);
}, [drawRef]);