Search code examples
javascriptreactjsnext.jsleafletreact-leaflet

Next.js React Leaflet Map not Showing Properly


I have the next leaflet map:

enter image description here

But is not showing anything properly, only when I drag the map and only loads the parts that you can see in the upper left corner, as the second image.

I have discovered that the error only happens at the first render, If I navigate to other page without refresh (next Link component) then it appears correctly.

My code:

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

function Map({ coordinates }) {
   const [position, setPosition] = useState(coordinates);
   return (
      <MapContainer center={position} zoom={100} scrollWheelZoom={false} style={{ height: "500px", width: "100%" }}>
         <TileLayer attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
         <Marker position={position}>
            <Popup>
               A pretty CSS3 popup. <br /> Easily customizable.
            </Popup>
         </Marker>
      </MapContainer>
   );
}

export default Map;

I have installed react-reaflet and leaflet, I also imported the CSS and specified the width and the height of the container, as others solutions in Stack Overflow indicates, but nothing works.

How I import the component in Next (because leaflet does not support Server Side Rendering):

/**
 * Leaflet makes direct calls to the DOM when it is loaded, therefore React Leaflet is not compatible with server-side rendering.
 * @see https://stackoverflow.com/a/64634759/9244579
 */
const Map = dynamic(() => import("../../components/Map"), {
   loading: () => <p>Map is loading</p>,
   ssr: false, // This line is important. It's what prevents server-side render
});

Solution

  • I just solved the problem.

    I discovered that if I resize the window the map shows properly, so what I had to do is to call invalidateSize() function every 100 miliseconds for having the size of the screen updated all the time, this will work at the first render too.

    import React, { useState, useEffect } from "react";
    import { MapContainer, Circle, TileLayer } from "react-leaflet";
    import "leaflet/dist/leaflet.css";
    
    function Map({ coordinates }) {
       const position = coordinates;
       const fillBlueOptions = { fillColor: "#0484D6" };
       const [map, setMap] = useState(null);
    
       useEffect(() => {
          if (map) {
             setInterval(function () {
                map.invalidateSize();
             }, 100);
          }
       }, [map]);
    
       return (
          <MapContainer center={position} zoom={20} scrollWheelZoom={false} style={{ height: "400px", width: "100%" }} whenCreated={setMap}>
             <TileLayer attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
             <Circle center={position} pathOptions={fillBlueOptions} radius={50} />
          </MapContainer>
       );
    }
    
    export default Map;
    

    Take into consider that you have to add the propert whenCreated={setMap} to the MapContainer component.