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 © 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!