1

I have a React v18.2 (TS) application where I need to display Google maps to my users. I need the drawing functionality, a.k.a. geofence so my users can draw polygon shapes on the map. I have integrated the map using @react-google-maps/api v2.18.1 and everything works perfect IF I am NOT using React.StrictMode. Can someone help me with this issue? I need my React.StrictMode turned ON and still have the Geofence UI rendered on my maps so my users can use it. How is this related to React strict mode and is there a way to fix this?

This is my Map component by the way:


import { Polygon, GoogleMap, useLoadScript } from '@react-google-maps/api';

import { DrawingManager } from './drawing-manager';

interface MapsProps {
  lat: number;
  lng: number;
  isStatic: boolean;

  zoom?: number;
  isDrawable?: boolean;
}

interface Shape {
  type: string;
  path: google.maps.LatLngLiteral[];
  radius?: number;
}

const { useRef, useMemo, useState, useCallback } = React;
const __mapMandatoryStyles = { width: '100%', height: '100%' }; // ⌂ Keep it like this and style the parent if needed!
const googleMapsApiKey = process.env.REACT_APP_GOOGLE_MAPS_API_KEY as string;
const libraries: ('drawing' | 'geometry' | 'localContext' | 'places' | 'visualization')[] = [
  'drawing',
]; // ⌂ Do NOT touch this type! It comes from the '@react-google-maps/api' lib but it's not exported from there.

export const Map = ({
  lat,
  lng,
  isStatic,
  zoom = 15,
  isDrawable = false,
}: MapsProps): JSX.Element | null => {
  const [shapes, setShapes] = useState<Array<Shape>>([]);

  const { isLoaded } = useLoadScript({
    id: 'google-map-script',
    googleMapsApiKey,
    libraries,
  });

  const mapRef = useRef<google.maps.Map | null>(null);
  const drawingManagerRef = useRef(null);

  const handleMapLoad = useCallback((map: google.maps.Map) => {
    if (mapRef?.current) {
      mapRef.current = map;
    }
  }, []);

  const handleDraw = useCallback(
    (shape: Shape) => {
      setShapes([...shapes, shape]);
    },
    [shapes]
  );

  const handleOnPolygonComplete = useCallback(
    (polygon: google.maps.Polygon) => {
      if (polygon.getPath() !== null) {
        const polygonPathArray = polygon.getPath().getArray();

        if (polygonPathArray) {
          const path = polygonPathArray.map(latLngLiteral => {
            return { lat: latLngLiteral.lat(), lng: latLngLiteral.lng() };
          });

          handleDraw({ path, type: 'polygon' });
        }
      }
    },
    [handleDraw]
  );

  const renderShapes = useCallback(
    () =>
      shapes.map((shape, idx): JSX.Element | null =>
        shape.type === 'polygon' ? (
          <Polygon key={idx} path={shape.path.map(latLng => latLng)} />
        ) : null
      ),
    [shapes]
  );

  const getMapOptions = useMemo(
    () => ({
      zoomControl: !isStatic,
      scrollwheel: !isStatic,
      rotateControl: !isStatic,
      clickableIcons: !isStatic,
      mapTypeControl: !isStatic,
      keyboardShortcuts: !isStatic,
      fullscreenControl: !isStatic,
      streetViewControl: !isStatic,
      disableDoubleClickZoom: isStatic,
      isFractionalZoomEnabled: !isStatic,
      gestureHandling: isStatic ? 'none' : 'auto',
    }),
    [isStatic]
  );

  if (isDrawable) {
    return isLoaded ? (
      <GoogleMap
        zoom={zoom}
        center={{ lat, lng }}
        options={getMapOptions}
        mapContainerStyle={__mapMandatoryStyles}
        onLoad={handleMapLoad}
      >
        <DrawingManager
          ref={drawingManagerRef}
          options={{
            drawingControl: true,
            drawingControlOptions: {
              position: google.maps.ControlPosition.TOP_CENTER,
              drawingModes: [google?.maps?.drawing?.OverlayType.POLYGON],
            },
          }}
          onPolygonComplete={handleOnPolygonComplete}
        />

        {renderShapes()}
      </GoogleMap>
    ) : null;
  }

  return isLoaded ? (
    <GoogleMap
      zoom={zoom}
      center={{ lat, lng }}
      options={getMapOptions}
      mapContainerStyle={__mapMandatoryStyles}
    />
  ) : null;
};

The DrawingManager's code is:

import * as React from 'react';

import {
  DrawingManager as RactGoogleMapsDrawingManager,
  DrawingManagerProps,
} from '@react-google-maps/api';

export const DrawingManager = React.forwardRef<RactGoogleMapsDrawingManager, DrawingManagerProps>(
  (props, ref): JSX.Element => <RactGoogleMapsDrawingManager ref={ref} {...props} />
);

DrawingManager.displayName = 'DrawingManager';
starball
  • 20,030
  • 7
  • 43
  • 238
XxXtraCookie
  • 171
  • 2
  • 11
  • 1
    Have you tested this in both production and development? Reacts strict mode does some extra unmounting-remounting and re-running of side effects during development that can sometime cause issues. – super Apr 03 '23 at 07:21
  • @super Actually I've tested this only in development. I will test it in prod right away! Thank you! – XxXtraCookie Apr 03 '23 at 07:50

1 Answers1

2

Use DrawingManagerF instead of DrawingManager for React v18 and Up

It is undocumented but you should use the Components with F on its end, in your case, DrawingManagerF. Because the old components were not compatible with React 18.

Ref: 2.10.0 react@18 support, new functional versions of components

the F letter means Functional and using such components would enable you to use React.Strictmode without having issues with the DrawingManager during development phase.

Since the React.Strictmode is only used during development phase, your app should work fine during production phase even when you are using the old DrawingManager component. But if you want to use React.Strictmode during production, then you should use DrawingManagerF. This applies to all other components that you are going to use.

Ref to source code: https://github.com/JustFly1984/react-google-maps-api/blob/develop/packages/react-google-maps-api/src/components/drawing/DrawingManager.tsx

With that said, please do note that the public docs are outdated because of build problems as mentioned here.

Hope this helps!

Yrll
  • 1,619
  • 2
  • 5
  • 19