Search code examples
javascriptleafletvuejs3

Vue 3, leaflet: Map container is already initialized


I realized that there is the same issue with classic javascript. However, I aim to use the v-for and "'map'+user.id", :ref to dynamically create the longitude, latitude leaflet maps for each bootstrap card. I am wondering how to solve this problem in vue 3.

From the console, I confirm that each block could have a different id but the same class.

enter image description here

I found this post with vuejs2 ref when initiating multiple instances. It's been a while, now the docs illustrates the correct way to use ref in the method.

Here is the CodeSandBox. (rendering in the localhost, though)

Here is the relevant code:

<template>
  <div class="container">
    <div class="row">
      <div class="col-md-4 mb-3" v-for="user in users" :key="user.id">
      <div :id="'map'+user.id" :ref="setItemRef" class="map"></div> 
    </div>
  </div>
</template>

<script>
import users from '@/data/data.js'
import L from 'leaflet'

export default {
  data() {
    return {
      status: true,
      users: [],
      map: null,
      accessToken: "Token shown in the CodeSandBox"
    }
  },
methods: {
setItemRef(el) {
      if (el) {
        // reference: https://v3-migration.vuejs.org/breaking-changes/array-refs.html#migration-strategy
        // this.itemRefs.push(el)
        console.log(el)
        this.map = L.map(el).setView([51.505, -0.09], 13);
        L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
            attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
            maxZoom: 18,
            id: 'mapbox/streets-v11',
            tileSize: 512,
            zoomOffset: -1,
            accessToken: this.accessToken
        }).addTo(this.map);
        // this.map.off()
        // this.map.remove()
        //console.log(this.map)
      }
    },
},
</script>

<style>
.map {
  width: 400px;
  height: 400px;
}
</style>

Solution

  • The way I did it is to push the 'map'+user.id into an array. Then using forEach to render with different maps. The error I bumped into is because the this.map always refers to the same instance.

    Therefore, the code in the script should be:

    <script>
      export default {
      data() {
        return {
          status: true,
          users: [],
          itemRefs:[], // for saving the unique mapid
          accessToken: "Your mapbox token"
        }
      },
      methods: {
        /**
         * instantiate the id to each user to further use
         */
        instantiateId() {
          this.users.map((user,index) => user.id = index)
        },
        setItemRef(el) {
          if (el) {
            // reference: https://v3.vuejs.org/guide/migration/array-refs.html#migration-strategy
            //console.log(el)
            //console.log(el.id)
            this.itemRefs.push(el.id)
          }
        },
        createMap() {
          this.itemRefs.forEach(element => {
            // console.log(element)
            var mymap = L.map(element).setView([51.505, -0.09],13);
            L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
              attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
              maxZoom: 18,
              id: 'mapbox/streets-v11',
              tileSize: 512,
              zoomOffset: -1,
              accessToken: this.accessToken
            }).addTo(mymap);
          });
        }
      },  
      mounted() {
        this.createMap()
      },
    }
    </script>