'How to pass ref to draw control in react-map-gl

Following the Mapbox draw example I can use the draw variable to access all features that are drawn on a map.

const draw = new MapboxDraw({
 // ...
});
map.addControl(draw);
// ...
function updateArea(e) {
  const data = draw.getAll(); // Accessing all features (data) drawn here
  // ...
}

However, in react-map-gl library useControl example I can not figure out how to pass ref to the DrawControl component so I can use it as something like draw.current in a similar way as I did draw in normal javascript above.

In my DrawControl.jsx

const DrawControl = (props) => {
  useControl(
    ({ map }) => {
      map.on('draw.create', props.onCreate);
      // ...
      return new MapboxDraw(props);
    },
    ({ map }) => {
      map.off('draw.create', props.onCreate);
      // ...
    },{
      position: props.position,
    },
  );

  return null;
};

In my MapDrawer.jsx

import Map from 'react-map-gl';
import DrawControl from './DrawControl';
// ...
export const MapDrawer = () => {
  const draw = React.useRef(null);

  const onUpdate = React.useCallback((e) => {
    const data = draw.current.getAll(); // this does not work as expected
    // ...
  }, []);

  return (
    // ...
    <Map ...>
      <DrawControl
        ref={draw}
        onCreate={onUpdate}
        onUpdate={onUpdate}
        ...
      />
    </Map>
  )
}

I also get an error stating I should use forwardRef but I'm not really sure how.

react_devtools_backend.js:3973 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

What I need is basically to delete the previous feature if there is a new polygon drawn on a map so that only one polygon is allowed on a map. I want to be able to do something like this in the onUpdate callback.

const onUpdate = React.useCallback((e) => {
  // ...
  draw.current.delete(draw.current.getAll.features[0].id);
  // ...
}, []);


Solution 1:[1]

I had the similar problem recently with that lib, I solved it doing the following :

export let drawRef = null; 
export default const DrawControl = (props) => {
  drawRef = useControl(
    ({ map }) => {
      map.on('draw.create', props.onCreate);
      // ...
      return new MapboxDraw(props);
    },
    ({ map }) => {
      map.off('draw.create', props.onCreate);
      // ...
    },{
      position: props.position,
    },
  );

  return null;
};
import DrawControl, {drawRef} from './DrawControl';
// ...
export const MapDrawer = () => {
  const draw = drawRef;

  const onUpdate = React.useCallback((e) => {
    const data = draw?draw.current.getAll():null; // this does not work as expected
    // ...
  }, []);

  return (
    // ...
    <Map ...>
      <DrawControl
        onCreate={onUpdate}
        onUpdate={onUpdate}
        ...
      />
    </Map>
  )
}
const onUpdate = React.useCallback((e) => {
  // ...
  drawRef.delete(drawRef.getAll.features[0].id);
  // ...
}, []);

Once component created, the ref is available for use. Not that elegant but working... Sure there might be cleaner way...

Hope that helps! Cheers

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 limaNz