react-leaflet
react-leaflet copied to clipboard
Icon does not show: Nextjs + Leaflet bug?
Bug report in v4
Before opening an issue, make sure to read the contributing guide and understand this is a bug tracker, not a support platform.
Please make sure to check the following boxes before submitting an issue.
Issues opened without using this template will be closed unless they have a good reason not to follow this template.
- [X ] All peer dependencies are installed: React, ReactDOM and Leaflet.
- [X ] Using the latest version of React and ReactDOM v18.
- [ X] Using the supported version of Leaflet (v1.8.0 minimum) and its corresponding CSS file is loaded.
- [ X] Using the latest v4 version of React-Leaflet.
- [x] The issue has not already been reported.
- [ X] Make sure you have followed the quick start guide for Leaflet.
- [ ]X Make sure you have fully read the documentation and that you understand the limitations.
Expected behavior
Get an icon to show up. The default one or a custom one. Both don't work.
Actual behavior
It's a simple one. I'm trying to get the Icon to work. The default icon does not show up. Also the custom icon (where the path is correct) does not show up.
https://github.com/Tarshh/waar-laden/blob/master/src/components/Map/Map.tsx This is the file, the css is loaded on the page itself. Changing the place where the CSS loads does not work either. I know Leaflet does not work with SSR, hence the "use client" on top of the file
I'm 99% sure everything is installed correctly?
Steps to reproduce
Here is the full repo. https://github.com/Tarshh/waar-laden I'm thinking that the issue is a combination with Nextjs and Leaflet? I tried making a new project just now and have the same result.
Thanks in advance...
I think that the solution lies in adding the following code to the imports section:
import {
MapContainer,
TileLayer,
Marker,
Popup,
useMapEvent,
} from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png").default,
iconUrl: require("leaflet/dist/images/marker-icon.png").default,
shadowUrl: require("leaflet/dist/images/marker-shadow.png").default,
});
I was able to fix this issue by customizing the market icon and passed the icons props to the Marker component. See the code snippet:
https://github.com/PaulLeCam/react-leaflet/issues/808#issuecomment-1605338973
I think that the solution lies in adding the following code to the imports section:
import { MapContainer, TileLayer, Marker, Popup, useMapEvent, } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png").default, iconUrl: require("leaflet/dist/images/marker-icon.png").default, shadowUrl: require("leaflet/dist/images/marker-shadow.png").default, });
Hello @CodePhilanthropist !
Can you tell me about your solution fully? How can I add icons globally? Because I am not only using react-leaflet. To edit the points or polygons I am using react-leaflet-geoman-v2 library!
The second thing I am going to ask you! When the data comes from API react-leaflet-geoman-v2 is not editing existing data that was not drawn by itself! After creating the point, I am saving it to the DB. But when I send get request I am getting nothing to change. But the shapes inside of the box are visible.
import * as React from "react";
import { FeatureGroup } from "react-leaflet";
import type { FeatureCollection } from "geojson";
import * as L from "leaflet";
import { GeomanControls } from "react-leaflet-geoman-v2";
export interface Props {
geojson: FeatureCollection;
cutPolygon: boolean;
drawPolygon: boolean;
setGeojson: (geojson: FeatureCollection) => void;
}
export default function GeomanStation({
geojson,
setGeojson,
cutPolygon = false,
drawPolygon = false,
}: Props) {
const ref = React.useRef<L.FeatureGroup>(null);
const [markerCreated, setMarkerCreated] = React.useState(false);
const generateId = () => {
let uniqueId = React.useId();
return uniqueId;
};
React.useEffect(() => {
if (ref.current?.getLayers().length === 0 && geojson) {
L.geoJSON(geojson).eachLayer((layer) => {
console.log(layer);
if (
layer instanceof L.Polyline ||
layer instanceof L.Polygon ||
layer instanceof L.Marker
) {
if (layer?.feature?.properties.radius && ref.current) {
new L.Circle(layer.feature.geometry.coordinates.slice().reverse(), {
radius: layer.feature?.properties.radius,
}).addTo(ref.current);
} else {
ref.current?.addLayer(layer);
}
}
});
}
if (!geojson.features.length) {
setMarkerCreated(false);
}
}, [geojson, markerCreated, geojson.features.length]);
const handleChange = () => {
const newGeo: FeatureCollection = {
type: "FeatureCollection",
features: [],
};
const layers = ref.current?.getLayers();
if (layers) {
layers.forEach((layer) => {
if (layer instanceof L.Circle || layer instanceof L.CircleMarker) {
const { lat, lng } = layer.getLatLng();
newGeo.features.push({
type: "Feature",
properties: {
radius: layer.getRadius(),
},
geometry: {
type: "Point",
coordinates: [lng, lat],
},
id: generateId(),
});
} else if (
layer instanceof L.Marker ||
layer instanceof L.Polygon ||
layer instanceof L.Rectangle ||
layer instanceof L.Polyline
) {
newGeo.features.push(layer.toGeoJSON());
}
});
}
if (!markerCreated) {
const markerLayer = ref.current
?.getLayers()
.find((layer) => layer instanceof L.Marker);
if (markerLayer) {
setMarkerCreated(true);
}
}
setGeojson(newGeo);
};
return (
<FeatureGroup ref={ref}>
<GeomanControls
options={{
position: "topleft",
drawText: false,
drawCircle: false,
drawCircleMarker: false,
drawRectangle: false,
drawPolygon,
cutPolygon,
drawMarker: !markerCreated,
disableMarkerInsert: markerCreated,
}}
globalOptions={{
continueDrawing: markerCreated,
editable: false,
}}
// onMount={() => L.PM.setOptIn(true)}
// onUnmount={() => L.PM.setOptIn(false)}
eventDebugFn={console.log}
onCreate={handleChange}
onChange={handleChange}
onUpdate={handleChange}
onEdit={handleChange}
onMapRemove={handleChange}
onMapCut={handleChange}
onDragEnd={handleChange}
onMarkerDragEnd={handleChange}
/>
</FeatureGroup>
);
}
I was able to fix this issue by customizing the market icon and passed the icons props to the Marker component. See the code snippet:
Did not work, I even putted the url's straight in the icon object. Does not work
"use client";
import {
Circle,
MapContainer,
Marker,
Popup,
TileLayer,
useMapEvent,
} from "react-leaflet";
import { useEffect, useState } from "react";
import marker from "public/icons/energyIcon.svg";
import L, { Icon } from "leaflet";
import Image from "next/image";
import "leaflet/dist/leaflet.css";
import iconMarker from "public/icons/energyIcon.svg";
export function Map() {
const customMarkerIcon = L.icon({
iconUrl: "leaflet/dist/images/marker-icon.png",
iconRetinaUrl: "leaflet/dist/images/marker-icon-2x.png",
shadowUrl: "leaflet/dist/images/marker-shadow.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41],
});
const [data, setData]: any = useState();
async function fetchData() {
const response = await fetch(url);
try {
const responseJson = await response.json();
setData(responseJson);
} catch (e) {
console.error(e);
}
}
const myIcon = new Icon({
iconUrl: marker,
iconSize: [32, 32],
});
useEffect(() => {
async function fetchDataAsync() {
await fetchData();
}
fetchDataAsync();
}, []);
return (
<MapContainer
center={[51.11728, 4.4182]}
zoom={10}
style={{ height: "100vh", width: "100vw" }}
>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{data && (
<>
<Marker position={data.items[0].position} icon={customMarkerIcon}>
<Popup>Works</Popup>
</Marker>
<Circle
center={data.items[0].position}
pathOptions={{ color: "#50C878", fillColor: "#72FE9F" }}
radius={200}
/>
</>
)}
</MapContainer>
);
}
I added a circlie and that works like it should. Still not getting it to work.
Such a basic thing, i'm really wondering what I'm doing wrong
I think that the solution lies in adding the following code to the imports section:
import { MapContainer, TileLayer, Marker, Popup, useMapEvent, } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png").default, iconUrl: require("leaflet/dist/images/marker-icon.png").default, shadowUrl: require("leaflet/dist/images/marker-shadow.png").default, });
This one errors for me.
I have found a workaround that uses a custom icon. This requires to host your own Icon.
Currently the iconUrl works well with absolute URL, but no relative path
<Marker
position={[51.505, -0.09]}
icon={L.icon({
iconUrl: `${window.location.origin}/images/favicon.png`,
iconSize: [40, 41],
})}
>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
I think that the solution lies in adding the following code to the imports section:
import { MapContainer, TileLayer, Marker, Popup, useMapEvent, } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png").default, iconUrl: require("leaflet/dist/images/marker-icon.png").default, shadowUrl: require("leaflet/dist/images/marker-shadow.png").default, });
This one errors for me.
That's because the typescript types are not complete as you can see in https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/leaflet/index.d.ts#L3007 . The method exists https://github.com/Leaflet/Leaflet/blob/main/src/layer/marker/Icon.Default.js#L33 , but there's no need to delete it using ES6 import syntax. This should do
import L from 'leaflet';
import MarkerIcon from 'leaflet/dist/images/marker-icon.png';
import MarkerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
import MarkerShadow from 'leaflet/dist/images/marker-shadow.png';
L.Icon.Default.mergeOptions({
iconRetinaUrl: MarkerIcon2x.src,
iconUrl: MarkerIcon.src,
shadowUrl: MarkerShadow.src,
});
import { Icon } from "leaflet";
import MarkerIcon2X from "leaflet/dist/images/marker-icon-2x.png";
import MarkerIcon from "leaflet/dist/images/marker-icon.png";
import MarkerShadow from "leaflet/dist/images/marker-shadow.png";
Icon.Default.mergeOptions({
iconRetinaUrl: MarkerIcon2X.src,
iconUrl: MarkerIcon.src,
shadowUrl: MarkerShadow.src,
});
Icon and related events are not working on Next.js 14 versions. Downgrading back to Nextjs 13 fixed this issue for me
I use leaflet-defaulticon-compatibility with NextJS 14.1 and React-Leaflet 4.2 to make the built-in icons work. Not sure whether it works with custom icons.
It is important to load the css
and js
from this library after leaflet
.
import 'leaflet/dist/leaflet.css';
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css';
import L from 'leaflet';
import 'leaflet-defaulticon-compatibility';
@ffrosch Thanks for the solution. That got me out of a lot of pain.