React-Leaflet marker files not found
Asked Answered
J

12

34

I've got very simple code to display a map using react-leaflet and place a marker on it. However, i get the following two errors in my browser console

GET http://localhost:8080/marker-icon-2x.png 404 (Not Found)

GET http://localhost:8080/marker-shadow.png 404 (Not Found)

I tried to fix this issue by downloading those two images and placing them at the root. It works. However, how can i change the URL the react-leaflet marker element looks for the marker images? I'd like to store them in "./images" rather than at the root.

Jamie answered 23/3, 2018 at 2:45 Comment(1)
Can you mark helpfull answer?Infusorian
I
98

Try to do this :)

React leaflet for some reason do not include images and you will need to reset default icons image.

Below is some working example, I hope it will solve your issue.

You also can add icons from one of public link

https://cdnjs.com/libraries/Leaflet.awesome-markers

import React, { Component } from 'react';
import L from 'leaflet';
import {
    Map, TileLayer, Marker, Popup
} from 'react-leaflet'
import 'leaflet/dist/leaflet.css';
import './style.css';


import icon from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';

let DefaultIcon = L.icon({
    iconUrl: icon,
    shadowUrl: iconShadow
});

L.Marker.prototype.options.icon = DefaultIcon;
Infusorian answered 7/7, 2018 at 10:30 Comment(3)
Thanks! I had an additional problem with those markers while zooming out: they "moved" and became inaccurate. You can fix this by adding 'iconSize: [25,41], iconAnchor: [12,41]' to your DefaultIconCallow
Thanks, Was trying to figure it out for few days.Artemas
Can't believe that a 2018 answer will help me more than the recent ones.Gapeworm
C
16

Adding answer for Next.js

  1. Copy over marker icon from node_modules/leaflet/dist/images to public/images something like /images/marker-icon.png

  2. Create Leaflet icon reference and use the reference in Marker

const icon = L.icon({ iconUrl: "/images/marker-icon.png" });

// some other code

<Marker key={obj.id} position={position} icon={icon}>

// rest of the code
Cryptograph answered 16/4, 2021 at 23:1 Comment(1)
works well in nextjs! TnxHamford
S
15

Here is the solution that worked for me:

I added the following lines in the top of the file:

import { MapContainer, TileLayer, Marker, Popup } 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
});
Sladen answered 2/7, 2021 at 17:7 Comment(3)
Thanks! The .default did the trick for me. Somehow I found my needed answer here :)Kendrickkendricks
I worked. I just copied this code and it worked. I have no idea why. Thank you.Rotunda
This worked a couple of months ago and now it gives a long list of errors starting with Uncaught Error: iconUrl not set in Icon options (see the docs).. Ends with The above error occurred in the <ForwardRef(ContainerComponent)> component:. Any idea what to make it work again.Sabu
P
6

It seems not all stuff is properly integrated together when using react, leaflet and react-leaflet. I had the same problem and found this

https://github.com/PaulLeCam/react-leaflet/issues/453

You need to setup leafelet itself again, as something brokes after importing leaflet.css.

Hope it helps

Persuader answered 4/9, 2018 at 11:12 Comment(0)
O
5

Copy all images from leaflet package to the public directory:

cp node_modules/leaflet/dist/images/* {PUBLIC_WEB_DIRECTORY}/leaflet_images/

Fix the path in Leaflet

import L from 'leaflet';
L.Icon.Default.imagePath='leaflet_images/';
Ostensory answered 21/3, 2021 at 11:3 Comment(0)
P
4

In 2024 you can just use leaflet-defaulticon-compatibility.

npm install leaflet-defaulticon-compatibility --save

Make sure to import the compatibility package after leaflet!

import 'leaflet/dist/leaflet.css';
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css'; // Re-uses images from ~leaflet package
import L from 'leaflet';
import 'leaflet-defaulticon-compatibility';

The position of your react-leaflet imports should not matter. This setup also works well with NextJS 14.

Perishable answered 8/2 at 18:54 Comment(2)
This worked for me. May I ask how did you know this is the fix? I spent a long time looking for a fix.Diverticulitis
I think I searched for something like "react leaflet next.js 14" an looked for Blogs that described setting up react leaflet with next.js. I figured the problem might be due to the framework I was using, because I had set up everything else according to the docs. The missing icon seems to be a problem in applications using bundlersPerishable
S
3

Elaborated answer from @ch4nd4n's answer and adapted for leaflet (v1.8.0) for reactjs (v17).

import iconMarker from 'leaflet/dist/images/marker-icon.png'
import iconRetina from 'leaflet/dist/images/marker-icon-2x.png'
import iconShadow from 'leaflet/dist/images/marker-shadow.png'

Then,

const icon = L.icon({ 
    iconRetinaUrl:iconRetina, 
    iconUrl: iconMarker, 
    shadowUrl: iconShadow 
});

Add icon prop to the Marker component.

<Marker key={index} position={[loc.lat, loc.long]} icon={icon}>
    <Popup><h3>{loc.name}</h3> {loc.address}</Popup>
</Marker>
Sabu answered 27/4, 2022 at 9:54 Comment(0)
G
1

What ended up fixing this for me was removing:

import 'leaflet/dist/leaflet.css';

from the file in which my map components was in. I ended up importing leaflet css through the create-react-app index.html file and my marker was able to load alongside my map. Hope this helps anyone stuck.

Granada answered 7/11, 2021 at 20:21 Comment(1)
Well, it's better for abstraction to keep the css import in your map component. Let's say you remove the map from your project; do you have to worry about scrubbing the entire codebase to look for map-related resources to remove as well?Difficile
D
1

Similar solution to @Daniel-James But for TypeScript.

//CSS and marker image fix for Leaflet map
import "leaflet/dist/leaflet.css";
import iconMarker from "leaflet/dist/images/marker-icon.png";
import iconRetina from "leaflet/dist/images/marker-icon-2x.png";
import iconShadow from "leaflet/dist/images/marker-shadow.png";
import L from "leaflet";
L.Icon.Default.mergeOptions({
  iconRetinaUrl: iconRetina,
  iconUrl: iconMarker,
  shadowUrl: iconShadow,
});

Add this code to the imports section of your script.

Explanation

As far as I understand, Leaflet is typically used from CDN, but in the case of using react we'll be using node modules instead. Therefore we need to re-point the CSS to that of the node module, and also repoint the default icons to point towards the node module too.

Dermott answered 6/12, 2022 at 8:54 Comment(0)
P
1

Because I was unable to find a full example that solved all of my issues, I'm posting my code. Working in January 2024 with the latest versions of leaflet, react-leaflet, react, and next.js. This example should allow the client to load markers (from the CDN), but also refresh the page even if it's being delivered from server-side contexts like next.js.

import React, { useEffect, useState } from 'react';

export const Map = ({ mapMarkers, height }) => {
  const [Leaflet, setLeaflet] = useState(null);
  const [MapContainer, setMapContainer] = useState(null);
  const [TileLayer, setTileLayer] = useState(null);
  const [Marker, setMarker] = useState(null);
  const [Popup, setPopup] = useState(null);

  useEffect(() => {
    import('react-leaflet').then((mod) => setMapContainer(mod.MapContainer));
    import('react-leaflet').then((mod) => setTileLayer(mod.TileLayer));
    import('react-leaflet').then((mod) => setMarker(mod.Marker));
    import('react-leaflet').then((mod) => setPopup(mod.Popup));
    import('leaflet').then((mod) => setLeaflet(mod));
  }, []); // Run imports just once on mount

  const [customIcon, setCustomIcon] = useState(null);
  const [initialBounds, setInitialBounds] = useState(null);

  useEffect(() => {
    if (Leaflet && mapMarkers.length > 0) {
      // Calculate the bounds based on marker positions
      const markerBounds = mapMarkers.map(({ position }) => position);
      const bounds = Leaflet.latLngBounds(markerBounds);

      // Pad the bounds to add some padding to the viewport
      const paddedBounds = bounds.pad(1); // Adjust the padding factor as needed

      // Set initial bounds for the MapContainer
      setInitialBounds(paddedBounds);

      // Initialize Leaflet once the component is mounted on the client side, and provide icons.
      const icon = new Leaflet.Icon({
        iconUrl: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/images/marker-icon.png',
        iconSize: [22, 32],
        iconAnchor: [16, 32],
        popupAnchor: [0, -32],
      });

      setCustomIcon(icon);
    }
  }, [Leaflet, mapMarkers]); // Run the effect whenever Leaflet or mapMarkers are updated

  
  return (
    MapContainer && initialBounds && (
      <MapContainer
        bounds={initialBounds}
        style={{ width: '100%', height: height || '400px' }}
      >
        <TileLayer
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        />
        {customIcon &&
          mapMarkers.map(({ position, popupContent }, index) => (
            <Marker key={index} position={position} icon={customIcon}>
              <Popup>{popupContent}</Popup>
            </Marker>
          ))}
      </MapContainer>
    )
  );
};

The component employs useEffect hooks to avoid Server Side Rendering. Because of the dependency on Leaflet.js, the map cannot be rendered server-side. Leaflet was built, and continues to be developed, for the context of rendering maps in standard browser environments. In such contexts, it is expected that Leaflet will have access to the Window object. This is categorically untrue in server side contexts, and so this component falls back on Client Side Rendering, using a CDN to deliver static assets (as Leaflet expects).

I have not taken the time to add types; please feel free to make that effort if you're so moved!

Peter answered 28/1 at 23:47 Comment(0)
I
0

For anyone attempting to use leaflet in an expo react native app, the marker image also won't load. A workaround is to use expo-asset, npx expo install expo-asset, and then place an icon in the assets folder. Then you'll need to get a uri using expo-asset and use that as the iconUrl for the leaflet icon.

import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import { Asset } from "expo-asset";

import React from "react";
import { StyleSheet } from "react-native";

const WebMap = () => {
  const icon = require('../../../assets/icon.png');
  const iconURI = Asset.fromModule(icon).uri;

  const leafletIcon = new L.Icon({
    iconUrl: iconURI,
    iconSize: [30, 30],
    iconAnchor: [22, 94],
    popupAnchor: [-3, -76],
  });
  return (
      <MapContainer
        center={[51.505, -0.09]}
        zoom={13}
        scrollWheelZoom={true}
        style={styles.container}
      >
        <TileLayer
          url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        />
        <Marker position={[51.505, -0.09]} icon={leafletIcon}>
          <Popup>
            A pretty CSS3 popup. <br /> Easily customizable.
          </Popup>
        </Marker>
      </MapContainer>
  );
};

const styles = StyleSheet.create({
  container: {
    width: "500px",
    height: "500px",
  },
});

export default WebMap;
Indecorum answered 5/8, 2023 at 16:44 Comment(0)
S
0

The easiest solution, the best:

import Leaflet from 'leaflet';
Leaflet.Icon.Default.imagePath = '/images/'
Sigismondo answered 1/4 at 19:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.