1

I would like to let users create area polygons on Leaflet maps. I have seen some nice examples of people creating this in React and JS (like this one: https://codesandbox.io/s/react-leaflet-draw-example-j3gw4), and I would be keen to do that in NextJs and TS.

This is my index.tsx:

import { type NextPage } from "next";  
import dynamic from "next/dynamic";

const MapWithNoSSR = dynamic(() => import("../components/Map"), {
  ssr: false,
});

const Home: NextPage = () => {

  return (
    <>
      <div className="z-0 mx-auto w-3/4">
        <MapWithNoSSR />
      </div>        
    </>
  );
};

export default Home;

Here is my Map.tsx:

import React from "react";
import { MapContainer, TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import DrawTools from "./DrawTools";

const Map: React.FC = () => {
  const mapConfig = {
    lat: 0.67,
    lng: 114.0,
    zoom: 6,
  };

  return (
    <MapContainer
      center={[mapConfig.lat, mapConfig.lng]}
      zoom={mapConfig.zoom}
      style={{ height: "100vh" }}
    >
      <DrawTools />
      <TileLayer
        attribution="Tiles &copy; Carto"
        url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
      />
    </MapContainer>
  );
};

export default Map;

And here is finally my DrawTools.tsx:

import React from "react";
import { FeatureGroup } from "react-leaflet";
import { EditControl } from "react-leaflet-draw";
import { LatLng, DivIcon, Point, Layer, LayerGroup } from "leaflet";
import type { DrawEvents } from "leaflet";

export default function DrawTools() {
  const _onEdited = (e: DrawEvents.Edited) => {
    let numEdited = 0;
    e.layers.eachLayer((layer: Layer) => {
      numEdited += 1;
    });
    console.log(`_onEdited: edited ${numEdited} layers`, e);
  };

  const _onCreated = (e: DrawEvents.Created) => {
    let type = e.layerType;
    let layer = e.layer;
    if (type === "marker") {
      console.log("_onCreated: marker created", e);
    } else {
      console.log("_onCreated: something else created:", type, e);
    }

    console.log("Geojson", layer.toGeoJSON());
    console.log("coords", (layer as any).getLatLngs());
  };

  const _onDeleted = (e: DrawEvents.Deleted) => {
    let numDeleted = 0;
    e.layers.eachLayer((layer: Layer) => {
      numDeleted += 1;
    });
    console.log(`onDeleted: removed ${numDeleted} layers`, e);
  };

  const _onDrawStart = (e: DrawEvents.DrawStart) => {
    console.log("_onDrawStart", e);
  };

  return (
    <FeatureGroup>
      <EditControl
        onDrawStart={_onDrawStart}
        position="topleft"
        onEdited={_onEdited}
        onCreated={_onCreated}
        onDeleted={_onDeleted}
        draw={{
          polyline: {
            icon: new DivIcon({
              iconSize: new Point(8, 8),
              className: "leaflet-div-icon leaflet-editing-icon",
            }),
            shapeOptions: {
              //guidelineDistance: 10,
              color: "navy",
              weight: 3,
            },
          },
          rectangle: false,
          circlemarker: false,
          circle: false,
          polygon: false,
        }}
      />
    </FeatureGroup>
  );
}

Without the DrawTools the map renders fine, but when I add it I get the following error:

Uncaught Error: No context provided: useLeafletContext() can only be used in a descendant of <MapContainer>
    at useLeafletContext (context.js:9:1)
    at EditControl (EditControl.js:30:34)
    at renderWithHooks (react-dom.development.js:16305:1)
    at mountIndeterminateComponent (react-dom.development.js:20074:1)
    at beginWork (react-dom.development.js:21587:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
    at invokeGuardedCallback (react-dom.development.js:4277:1)
    at beginWork$1 (react-dom.development.js:27451:1)
    at performUnitOfWork (react-dom.development.js:26557:1)
    at workLoopSync (react-dom.development.js:26466:1)
    at renderRootSync (react-dom.development.js:26434:1)
    at recoverFromConcurrentError (react-dom.development.js:25850:1)
    at performSyncWorkOnRoot (react-dom.development.js:26096:1)
    at flushSyncCallbacks (react-dom.development.js:12042:1)
    at eval (react-dom.development.js:25651:1)

Have been trying with dynamically importing the DrawTools component as well as including that code directly in the Map component (which I ideally don't want to do) but haven't been able to figure out a way past this.

In case it is helpful, this is my package.json:

{
  "name": "mapmaker",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "postinstall": "prisma generate",
    "lint": "next lint",
    "start": "next start"
  },
  "dependencies": {
    "@next-auth/prisma-adapter": "^1.0.5",
    "@prisma/client": "^4.11.0",
    "@tanstack/react-query": "^4.28.0",
    "@trpc/client": "^10.18.0",
    "@trpc/next": "^10.18.0",
    "@trpc/react-query": "^10.18.0",
    "@trpc/server": "^10.18.0",
    "@types/leaflet-draw": "^1.0.6",
    "leaflet": "^1.9.3",
    "leaflet-draw": "^1.0.4",
    "next": "^13.2.4",
    "next-auth": "^4.21.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-hooks-global-state": "^2.1.0",
    "react-leaflet": "^4.2.1",
    "react-leaflet-draw": "^0.20.4",
    "react-leaflet-fullscreen": "^2.0.2",
    "superjson": "1.12.2",
    "zod": "^3.21.4"
  },
  "devDependencies": {
    "@types/eslint": "^8.21.3",
    "@types/leaflet": "^1.9.3",
    "@types/node": "^18.15.5",
    "@types/prettier": "^2.7.2",
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@typescript-eslint/eslint-plugin": "^5.56.0",
    "@typescript-eslint/parser": "^5.56.0",
    "autoprefixer": "^10.4.14",
    "eslint": "^8.36.0",
    "eslint-config-next": "^13.2.4",
    "postcss": "^8.4.21",
    "prettier": "^2.8.6",
    "prettier-plugin-tailwindcss": "^0.2.6",
    "prisma": "^4.11.0",
    "tailwindcss": "^3.3.0",
    "typescript": "^5.0.2"
  },
  "ct3aMetadata": {
    "initVersion": "7.12.0"
  }
}

Grateful for all kinds of advice that could help solve this!

peternovak
  • 103
  • 1
  • 11

0 Answers0