Search code examples
next.jsleaflet

Nextjs 14, leaflet, dynamically adding markers to map


I am learning NextJS 14. My application is to display a leaflet map with some basic data. I've stated a new nextjs site using npx create-next-app@latest.

I've found an example NextJS and Leaflet starter repo at https://github.com/colbyfayock/next-leaflet-starter. I've adapted the components in this repo to my own nextjs site.

When trying to understand how to approach the problem, I find React Leaflet: Add markers dynamically which looks like it applies to an older version of nextjs. I get the concept though - the markers get addded to a State variable, then iterate the same State variable when drawing on the map.

I can tell from the console log that the solution should be working, but the dynamically added marker is not drawn on the map.

page.jsx code below, github: https://github.com/timatlee/nextjs-leaflet

"use client"

import { Button } from '@/components/ui/button'
import Map from '@components/Map';
import React from 'react';

const DEFAULT_CENTER = [38.907132, -77.036546]

export default function Home() {
  const [mapMarkers, setMapMarkers] = React.useState([[]])
  function doSomethingFun() {
    console.log("Start State ", mapMarkers)
    setMapMarkers([DEFAULT_CENTER])
  }

  return (
    <>
      <Map className="mapcomponent" width="800" height="400" center={DEFAULT_CENTER} zoom={13}>
        {({ TileLayer, Marker, Popup }) => (
          <>
            <TileLayer
              url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
              attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
            />

            <Marker position={[DEFAULT_CENTER[0] + 0.01, DEFAULT_CENTER[1] + 0.01]}>
              <Popup>
                A dynamically added marker and popup.
              </Popup>
            </Marker>
            {
              mapMarkers.map((latlng) => {
                if (latlng !== null) {
                  console.log("In map", latlng);
                  <Marker position={latlng}>
                    <Popup>
                      This popup should be offset from the dynamically drawn one.
                    </Popup>
                  </Marker>
                }
              })
            }
          </>
        )}
      </Map >
      <p>Hello.</p>
      <Button onClick={doSomethingFun}>Do something fun.</Button>

    </>
  )
}

Solution

  • Replace curly braces {} with parentheses () to properly return the Marker component in the mapMarkers.map function. And include the key prop for each dynamically added Marker.

    It can be done as follow:

    // ... (your imports)
    
    export default function Home() {
      const [mapMarkers, setMapMarkers] = React.useState([[]]);
    
      function doSomethingFun() {
        console.log("Start State ", mapMarkers);
        setMapMarkers([...mapMarkers, [DEFAULT_CENTER[0] + 0.02, DEFAULT_CENTER[1] - 0.02]]);
      }
    
      return (
        <>
          <Map className="mapcomponent" width="800" height="400" center={DEFAULT_CENTER} zoom={13}>
            {({ TileLayer, Marker, Popup }) => (
              <>
                <TileLayer
                  url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                  attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
                />
    
                <Marker position={[DEFAULT_CENTER[0] + 0.01, DEFAULT_CENTER[1] + 0.01]}>
                  <Popup>
                    A statically added marker and popup.
                  </Popup>
                </Marker>
    
                {mapMarkers.map((latlng, index) => (
                  latlng !== null && (
                    <Marker key={index} position={latlng}>
                      <Popup>
                        This popup should be offset from the dynamically drawn one.
                      </Popup>
                    </Marker>
                  )
                ))}
              </>
            )}
          </Map>
          <p>Hello.</p>
          <Button onClick={doSomethingFun}>Do something fun.</Button>
        </>
      );
    }