Strict Mode in React prompts Open Layer duplication errors

Viewed 30

Version: "ol": "^6.14.1",

I am trying to add a marker to an OpenLayer map in React, but whenever I click on the map I get the error: Uncaught AssertionError: Assertion failed. See https://openlayers.org/en/v6.14.1/doc/errors/#58 for details. which comes from the code map.addLayer(newMarkersLayer);.

The error states that: Duplicate item added to a unique collection. For example, it may be that you tried to add the same layer to a map twice. Check for calls to map.addLayer() or other places where the map's layer collection is modified.

This duplication happens because React.StricMode is wrapped around my app. If I disable strict mode, clicking on the map adds a marker.

I don't want to disable strict mode, because I still think that the error comes from my way of implementing the map in React using a badly written useEffect().

How should I add a marker on an OpenLayers map in React so that StrictMode doesn't adds duplicates ?

Map component:

import { useState, useEffect, useRef } from 'react';
// import ModalUI from '../UI/ModalUI';
import classes from './MapUI.module.css';
import { drawerActions } from '../Rooms/Drawers/drawerSlice';

import 'ol/ol.css';
import { Map, View, Overlay, Feature } from 'ol';
import Point from 'ol/geom/Point';
import { Vector as VectorLayer } from 'ol/layer';
import VectorSource from 'ol/source/Vector';
import { fromLonLat, toLonLat } from 'ol/proj';
import { toStringHDMS } from 'ol/coordinate';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';

import PopUp from './PopUp';
import { useDispatch } from 'react-redux';

export default function MapUI() {
  const dispatch = useDispatch();
  const mapRef = useRef();
  const popup = useRef();
  const [coordinates, setCoordinates] = useState('');
  const [newMarker, setNewMarker] = useState(
    new Feature({
      geometry: new Point([[]]),
      name: '',
    })
  );

  const [newMarkersLayer] = useState(
    new VectorLayer({
      properties: { name: 'newMarkers' },
      source: new VectorSource({
        features: [newMarker],
      }),
    })
  );

  const closePopup = () => {
    map.getOverlayById('map-popup').setPosition(undefined);
    map.removeLayer(newMarkersLayer);
  };

  const [map] = useState(
    new Map({
      target: '',
      layers: [
        new TileLayer({
          source: new OSM(),
        }),
        new VectorLayer({
          properties: { name: 'existingMarkers' },
          source: new VectorSource({
            // features: [marker],
          }),
        }),
      ],
      view: new View({
        center: fromLonLat([26.08, 44.46]),
        zoom: 15,
        minZoom: 10,
        maxZoom: 20,
      }),
    })
  );

  useEffect(() => {
    const overlay = new Overlay({
      element: popup.current,
      id: 'map-popup',
      autoPan: {
        animation: {
          duration: 250,
        },
      },
    });
    // console.log('useEffect in MapUI.jsx');

    map.addOverlay(overlay);
    map.setTarget(mapRef.current);
    map.on('singleclick', function (evt) {
      map.addLayer(newMarkersLayer);
      dispatch(drawerActions.closeDrawer());
      newMarker.getGeometry().setCoordinates(evt.coordinate);
      // console.log(typeof evt.coordinate);

      setCoordinates(toStringHDMS(toLonLat(evt.coordinate)));
      overlay.setPosition(evt.coordinate);
    });
  }, [newMarkersLayer, map, newMarker, dispatch]);

  return (
    <>
      <div
        style={{ height: '100%', width: '100%' }}
        ref={mapRef}
        className='map-container'
      />
      <div id='map-popup' className={classes['ol-popup']} ref={popup}>
        <PopUp coordinates={coordinates} closePopup={closePopup} />
      </div>
    </>
  );
}
1 Answers

React strict mode intentionally double calls certain handlers to force you to make clean and reusable components - which yours is not.

Either accept this fact and remove strict mode which will be only a hinderance for you, or - if you are planning to make reusable components - try to follow its guidelines. As Mike pointed out, you probably should not create a new layer on each click.

Also, I would like to point there are React components for OpenLayers (I am the author) at https://github.com/mmomtchev/rlayers

Related