How to setup a centralized state for a mapbox map in Vuex?

1.9k views Asked by At

I just started using vuex with vue. I do (roughly) understand the docs. I have a specific issue for which I am not sure whether I should use vuex and if so how to go about.

I have an app in which mapbox map(s) are omnipresent in various layouts and components etc. As I will be making several vue single file components but are working with one same instance of a mapbox map I think it makes sense to have the mapbox map initiated and managed in the vuex store. So e.g. when I change the map layout or something else, it will be reflected in all components.

When I continue in this direction I am puzzled by a couple of things:

  1. The map is not just a variable/ array, but an instance of the mapbox class map. So I suppose the initial state is an empty object and then it needs to be initialized. Correct?
  2. The initialization is asynchronous and can only happen after the page is loaded, I suppose. That might be why my code below is not working!?

I tried following:

Made a mapboxmap module, with

mapboxmap.js

import simple from '../../components/simplestyle'
let mapboxgl = require('mapbox-gl/dist/mapbox-gl.js')

// initial state
const state = {
  myMap: {},
  mapLoaded: false
}

const mutations = {
  loadMap (state, myMap) {
    state.myMap = myMap
    state.mapLoaded = true
  }
}

const actions = {
  loadMap (context) {
    'use strict'
    mapboxgl.accessToken = 'mysecretmapboxcode'
    let myMap = new mapboxgl.Map({
      container: 'map',
      style: simple,
      hash: true,
      center: [-74.0073, 40.7124],
      zoom: 16
    })
    context.commit('loadMap', myMap)
  }
}

export default {
  state,
  mutations,
  actions
}

And as component:

Maplayout.vue

<template>
  <div>
    <div id='map' class='map'>
    </div>
  </div>
</template>

<script type='text/babel'>
export default {
  mounted () {
    this.computed.myMapForView.set().then(() => this.computed.myMapForView.get())
  },
  computed: {
    myMapForView: {
      // getter
      get: function () {
        return this.$store.state.myMap
      },
      // setter
      set: function () {
        this.$store.dispatch('loadMap')
      }
    }
  }
}
</script>

Which does not work. Any suggestions on the solution approach and specific way to do this is much appreciated.

Error message I get in the browser:

vue.runtime.common.js?d43f:433 TypeError: Cannot read property 'myMapForView' of undefined
    at VueComponent.mounted (eval at 162 (0.ce2d9bf….js:21), <anonymous>:8:18)
    at callHook (eval at <anonymous> (app.js:794), <anonymous>:2335:19)
    at Object.insert (eval at <anonymous> (app.js:794), <anonymous>:2525:5)
    at invokeInsertHook (eval at <anonymous> (app.js:794), <anonymous>:4352:28)
    at VueComponent.patch [as __patch__] (eval at <anonymous> (app.js:794), <anonymous>:4508:5)
    at VueComponent.Vue._update (eval at <anonymous> (app.js:794), <anonymous>:2222:19)
    at VueComponent.eval (eval at <anonymous> (app.js:794), <anonymous>:2189:10)
    at Watcher.get (eval at <anonymous> (app.js:794), <anonymous>:1652:27)
    at Watcher.run (eval at <anonymous> (app.js:794), <anonymous>:1721:22)
    at flushSchedulerQueue (eval at <anonymous> (app.js:794), <anonymous>:1539:13)
logError @ vue.runtime.common.js?d43f:433

EDIT: After changing to this.myMapForView from this.computed.myMapForView I got following errormessage in browser:

vue.runtime.common.js?d43f:433 
TypeError: Cannot read property 'set' of undefined
    at VueComponent.mounted (eval at 162 (0.85b2be9….js:21), <anonymous>:6:22)
    at callHook (eval at <anonymous> (app.js:794), <anonymous>:2335:19)
    at Object.insert (eval at <anonymous> (app.js:794), <anonymous>:2525:5)
    at invokeInsertHook (eval at <anonymous> (app.js:794), <anonymous>:4352:28)
    at VueComponent.patch [as __patch__] (eval at <anonymous> (app.js:794), <anonymous>:4508:5)
    at VueComponent.Vue._update (eval at <anonymous> (app.js:794), <anonymous>:2222:19)
    at VueComponent.eval (eval at <anonymous> (app.js:794), <anonymous>:2189:10)
    at Watcher.get (eval at <anonymous> (app.js:794), <anonymous>:1652:27)
    at Watcher.run (eval at <anonymous> (app.js:794), <anonymous>:1721:22)
    at flushSchedulerQueue (eval at <anonymous> (app.js:794), <anonymous>:1539:13)
logError @ vue.runtime.common.js?d43f:433
1

There are 1 answers

7
Saurabh On BEST ANSWER

It seems to me is new mapboxgl.Map() is an asynchronous function and in vuex Actions can contain arbitrary asynchronous operations not mutations not from mutations. mutation handler functions must be synchronous.

So you should do new mapboxgl.Map() in actions like following:

import simple from '../../components/simplestyle'
let mapboxgl = require('mapbox-gl/dist/mapbox-gl.js')

// initial state
const state = {
  myMap: {},
  mapLoaded: false
}

const mutations = {
  loadMap (state, myMap) {
    state.myMap = myMap
  }
}

const actions = {
  loadMap (context) {
    'use strict'
    mapboxgl.accessToken = 'mysecretmapboxkey'
    var myMap = new mapboxgl.Map({
      container: 'map',
      style: simple,
      hash: true,
      center: [-74.0073, 40.7124],
      zoom: 16
    })
    context.commit('loadMap', myMap)
  }
}

export default {
  state,
  mutations,
  actions
}

Edit:

Given your mouse interactions are causing change in the state, you can have a computed property in you vue instance with getter and setter, like following:

computed: {
  myMapForView: {
    // getter
    get: function () {
      return this.$store.state. myMap
    },
    // setter
    set: function (newMap) {
      this.$store.commit('loadMap', newMap)
    }
  }
}

Working Fiddle : http://jsfiddle.net/aucqteLn/

Also check: Vuex store with "strict: true" does not work