@vis.gl/react-mapbox
Version: 
React components for Mapbox GL JS
82 lines (68 loc) • 2.95 kB
text/typescript
import * as React from 'react';
import {useImperativeHandle, useRef, useEffect, forwardRef, memo} from 'react';
import {applyReactStyle} from '../utils/apply-react-style';
import {useControl} from './use-control';
import type {
  ControlPosition,
  GeolocateControlInstance,
  GeolocateControlOptions
} from '../types/lib';
import type {GeolocateEvent, GeolocateResultEvent, GeolocateErrorEvent} from '../types/events';
export type GeolocateControlProps = GeolocateControlOptions & {
  /** Placement of the control relative to the map. */
  position?: ControlPosition;
  /** CSS style override, applied to the control's container */
  style?: React.CSSProperties;
  /** Called on each Geolocation API position update that returned as success. */
  onGeolocate?: (e: GeolocateResultEvent) => void;
  /** Called on each Geolocation API position update that returned as an error. */
  onError?: (e: GeolocateErrorEvent) => void;
  /** Called on each Geolocation API position update that returned as success but user position
   * is out of map `maxBounds`. */
  onOutOfMaxBounds?: (e: GeolocateResultEvent) => void;
  /** Called when the GeolocateControl changes to the active lock state. */
  onTrackUserLocationStart?: (e: GeolocateEvent) => void;
  /** Called when the GeolocateControl changes to the background state. */
  onTrackUserLocationEnd?: (e: GeolocateEvent) => void;
};
function _GeolocateControl(props: GeolocateControlProps, ref: React.Ref<GeolocateControlInstance>) {
  const thisRef = useRef({props});
  const ctrl = useControl(
    ({mapLib}) => {
      const gc = new mapLib.GeolocateControl(props);
      // Hack: fix GeolocateControl reuse
      // When using React strict mode, the component is mounted twice.
      // GeolocateControl's UI creation is asynchronous. Removing and adding it back causes the UI to be initialized twice.
      const setupUI = gc._setupUI.bind(gc);
      gc._setupUI = args => {
        if (!gc._container.hasChildNodes()) {
          setupUI(args);
        }
      };
      gc.on('geolocate', e => {
        thisRef.current.props.onGeolocate?.(e as GeolocateResultEvent);
      });
      gc.on('error', e => {
        thisRef.current.props.onError?.(e as GeolocateErrorEvent);
      });
      gc.on('outofmaxbounds', e => {
        thisRef.current.props.onOutOfMaxBounds?.(e as GeolocateResultEvent);
      });
      gc.on('trackuserlocationstart', e => {
        thisRef.current.props.onTrackUserLocationStart?.(e as GeolocateEvent);
      });
      gc.on('trackuserlocationend', e => {
        thisRef.current.props.onTrackUserLocationEnd?.(e as GeolocateEvent);
      });
      return gc;
    },
    {position: props.position}
  );
  thisRef.current.props = props;
  useImperativeHandle(ref, () => ctrl, []);
  useEffect(() => {
    applyReactStyle(ctrl._container, props.style);
  }, [props.style]);
  return null;
}
export const GeolocateControl = memo(forwardRef(_GeolocateControl));