react-leaflet icon indicating copy to clipboard operation
react-leaflet copied to clipboard

Icon does not show: Nextjs + Leaflet bug?

Open Tarshh opened this issue 1 year ago • 11 comments

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...

Tarshh avatar Jun 18 '23 19:06 Tarshh

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,
});

Victorr-Hugo avatar Jun 22 '23 04:06 Victorr-Hugo

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

CasualEngineerZombie avatar Jun 24 '23 09:06 CasualEngineerZombie

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>
  );
}

Abbosbek-cloud avatar Jun 27 '23 00:06 Abbosbek-cloud

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:

#808 (comment)

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='&copy; <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 image

Tarshh avatar Jun 27 '23 20:06 Tarshh

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,
});

image

This one errors for me.

Tarshh avatar Jun 27 '23 20:06 Tarshh

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>

marcozzxx810 avatar Jun 29 '23 14:06 marcozzxx810

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,
});

image

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,
});

demedos avatar Nov 06 '23 21:11 demedos

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,
});

mulfyx avatar Dec 18 '23 20:12 mulfyx

Icon and related events are not working on Next.js 14 versions. Downgrading back to Nextjs 13 fixed this issue for me

Bisi0n avatar Feb 01 '24 11:02 Bisi0n

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 avatar Feb 08 '24 17:02 ffrosch

@ffrosch Thanks for the solution. That got me out of a lot of pain.

Ed-TheVisionMaker avatar May 13 '24 16:05 Ed-TheVisionMaker