CRUD operations with React Leaflet Typescript

Viewed 182

I'm trying to implement a simple map editor, using React Leaflet on Typescript, in combination with react-leaflet-draw's EditControl.

The control should receive, in its properties, a set of blocks (a block is, basically, a polygon, with a serverId), and a set of handlers, called when a block is added/updated/deleted.

Displaying the initial blocks is fine, but when a block is edited or deleted, I don't manage to get the serverId of the modified/removed block.

How can this information be retrieved?

Here's my implementation:

import React, { FC } from 'react';
import { MapContainer, TileLayer, FeatureGroup, Polygon } from 'react-leaflet';
import { EditControl } from 'react-leaflet-draw';
import { LatLng } from 'leaflet';
import 'leaflet-draw/dist/leaflet.draw.css';
import 'leaflet-fullscreen/dist/Leaflet.fullscreen';
import 'leaflet-fullscreen/dist/leaflet.fullscreen.css';
import { If, Then } from 'react-if';

export interface Coordinate {
  latitude: number;
  longitude: number;
}
export interface Block {
  serverId: number;
  coordinates: Coordinate[];
}

interface MapEditorProps {
  blocks: Block[];
  onBlockCreated?: (coordinates: Coordinate[]) => void;
  onBlockDeleted?: (blockId: number) => void;
  onBlockUpdated?: (block: Block) => void;
}

const MapEditor: FC<MapEditorProps> = ({ blocks, onBlockCreated, onBlockDeleted, onBlockUpdated }: MapEditorProps) => {
  const onCreated = (e) => {
    const { layerType, layer } = e;
    if (layerType === 'polygon') {
      if (onBlockCreated !== undefined) {
        const latlngs = layer.getLatLngs()[0];
        onBlockCreated(latlngs.map((l: LatLng) => ({ latitude: l.lat, longitude: l.lng } as Coordinate)));
      }
    }
  };
  const onDeleted = (e) => {
    if (onBlockDeleted !== undefined) {
      // How to fetch the id of the deleted block?
      // const blockId = ???
      // onBlockDeleted (polygonId);
    }
  };
  const onUpdated = (e) => {
    if (onBlockUpdated !== undefined) {
      // How to fetch the modified block?
      // const updatedBlock = ???;
      // onBlockUpdated (updatedBlock);
    }
  };

  return (
    <div>
      <MapContainer
        style={{ height: '100vh', width: '100wh' }}
        center={{ lat: 44.72, lng: 8.4 }}
        zoom={15}
        fullscreenControl
      >
        <TileLayer
          attribution=""
          url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
        />

        <FeatureGroup>
          <EditControl
            position="topright"
            onCreated={onCreated}
            onEdited={onUpdated}
            onDeleted={onDeleted}
            draw={{
              rectangle: false,
              polyline: false,
              circle: false,
              circlemarker: false,
              marker: true
            }}
          />
          <If condition={blocks !== null}>
            <Then>
              {blocks.map((block) => (
                <Polygon key={block.serverId} positions={block.coordinates.map((c) => [c.latitude, c.longitude])} />
              ))}
            </Then>
          </If>
        </FeatureGroup>
      </MapContainer>
    </div>
  );
};
export default MapEditor;
1 Answers

I ended up creating a custom Polygon, PolygonBlock and its related PolygonBlockProps. The new PolygonBlockProps interface extends the original PolygonProps with the serverId:

export interface PolygonBlockProps extends PolygonProps {
  serverId?: number;
}

const PolygonBlock: FC<PolygonBlockProps> = (props: PolygonBlockProps) => <Polygon {...props} />;

The map is then populated with the new PolygonBlocks

          {blocks.map((block) => (
            <PolygonBlock
              key={block.serverId}
              positions={block.coordinates.map((c) => [c.latitude, c.longitude])}
              serverId={block.serverId}
            />
          ))}

When handling the events, the involved polygons are fetched inspecting the event's .layers.getLayers(), whose serverId is now available in the .options member:

  const onUpdated = (e) => {
    if (onBlockUpdated !== undefined) {
      const rawModifiedLayers = e.layers.getLayers();
      // Fetch the modified blocks
      const updatedBlocks = rawModifiedLayers.map((polygon) => ({
        serverId: polygon.options.serverId,
        coordinates: polygon.getLatLngs()[0].map((l: LatLng) => ({ latitude: l.lat, longitude: l.lng } as Coordinate))
      }));
      onBlockUpdated(updatedBlocks);
    }
  };

Here's the full code:

import React, { FC } from 'react';
import { MapContainer, TileLayer, FeatureGroup, Polygon, PolygonProps } from 'react-leaflet';
import { EditControl } from 'react-leaflet-draw';
import { LatLng } from 'leaflet';
import 'leaflet-draw/dist/leaflet.draw.css';
import 'leaflet-fullscreen/dist/Leaflet.fullscreen';
import 'leaflet-fullscreen/dist/leaflet.fullscreen.css';
import { If, Then } from 'react-if';

export interface PolygonBlockProps extends PolygonProps {
  serverId?: number;
}

const PolygonBlock: FC<PolygonBlockProps> = (props: PolygonBlockProps) => <Polygon {...props} />;

export interface Coordinate {
  latitude: number;
  longitude: number;
}
export interface Block {
  serverId: number;
  coordinates: Coordinate[];
}

interface MapEditorProps {
  blocks: Block[];
  onBlockCreated?: (coordinates: Coordinate[]) => void;
  onBlocksDeleted?: (blockIds: number[]) => void;
  onBlockUpdated?: (blocks: Block[]) => void;
}

const MapEditor: FC<MapEditorProps> = ({ blocks, onBlockCreated, onBlocksDeleted, onBlockUpdated }: MapEditorProps) => {
  const onCreated = (e) => {
    const { layerType, layer } = e;
    if (layerType === 'polygon') {
      if (onBlockCreated !== undefined) {
        const latlngs = layer.getLatLngs()[0];
        onBlockCreated(latlngs.map((l: LatLng) => ({ latitude: l.lat, longitude: l.lng } as Coordinate)));
      }
    }
  };
  const onDeleted = (e) => {
    if (onBlocksDeleted !== undefined) {
      const rawModifiedLayers = e.layers.getLayers();
      // Fetch the modified block ids
      const deletedBlockIds = rawModifiedLayers.map((polygon) => polygon.options.serverId);
      onBlocksDeleted(deletedBlockIds);
    }
  };

  const onUpdated = (e) => {
    if (onBlockUpdated !== undefined) {
      const rawModifiedLayers = e.layers.getLayers();
      // Fetch the modified blocks
      const updatedBlocks = rawModifiedLayers.map((polygon) => ({
        serverId: polygon.options.serverId,
        coordinates: polygon.getLatLngs()[0].map((l: LatLng) => ({ latitude: l.lat, longitude: l.lng } as Coordinate))
      }));
      onBlockUpdated(updatedBlocks);
    }
  };

  return (
    <div>
      <MapContainer
        style={{ height: '100vh', width: '100wh' }}
        // center={{ lat: 44.710293, lng: 8.081944 }}
        center={{ lat: 44.72, lng: 8.4 }}
        zoom={15}
        //  scrollWheelZoom={false}
        fullscreenControl
      >
        <TileLayer
          attribution=""
          url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
        />

        <FeatureGroup>
          <EditControl
            position="topright"
            onCreated={onCreated}
            onEdited={onUpdated}
            onDeleted={onDeleted}
            draw={{
              rectangle: false,
              polyline: false,
              circle: false,
              circlemarker: false,
              marker: true
            }}
          />
          <If condition={blocks !== null}>
            <Then>
              {blocks.map((block) => (
                <PolygonBlock
                  key={block.serverId}
                  positions={block.coordinates.map((c) => [c.latitude, c.longitude])}
                  serverId={block.serverId}
                />
              ))}
            </Then>
          </If>
        </FeatureGroup>
      </MapContainer>
    </div>
  );
};
export default MapEditor;
Related