clojurescriptmapbox-gl-jsclojurescript-javascript-interop

MapBox in cljs: Removing markers from map (after storing them in an atom)


Background:

In mapbox-gl-js, while you can remove layers and features from a map (because the references are stored), you cannot do the same with markers. Instead one has to store the reference to any added marker, otherwise one won't be able to remove them later.

var marker = new mapboxgl.Marker().addTo(map);
marker.remove();

Setup:

I have an atom where I add every marker I create, so I can later clean them.

(defonce markers (r/atom []))

(defn add-marker [map img coordinate]
  (let [marker (create-marker img)]
      (.setLngLat marker (clj->js coordinate))
      (.addTo marker map)
      (swap! markers conj marker)))

(defn clear-markers []
  (doseq [m (array-seq markers)] (.remove m))
  (reset! markers []))

If I call clear-markers however, nothing happens. No error, no warning, the marker just stays in the map.

If I remove the marker right after adding (just to try it out), it works as described in the docs:

(defn test-marker [map img coordinate]
  (let [marker (create-marker img)]
      (.setLngLat marker (clj->js coordinate))
      (.addTo marker map)
      (.remove marker)))

Obviously, with this code, the marker will be removed right after adding and thus never be on the map, which is not the desired behaviour, just a test.

I also tried other approaches on how to call .remove on the elements of the vector, the following was my very first try:

(defn clear-markers []
  (map #(.remove %) markers))

I'm fairly new to Clojure(Script), so I try to understand where my mistake is.


Solution

  • Just a quick guess, try replacing map with doseq here:

    (defn clear-markers []
      (doseq [marker @markers]
        (.remove marker)))
    

    The map function is lazy and won't run until it has to. Since it appears you are after a side-effect to remove markers, doseq is the right choice. It is intended for side-effects and always runs immediately. It always returns nil.

    Also, you need to deref markers to get a vector, then just use that in doseq. Do not use array-seq since the atom stores a plain Clojure/Script vector, not a JS array.

    Another tip: always prefer mapv to map. It is eager and removes many timing- & lazy-related issues. Be sure to study the Clojure CheatSheet and the CLJS version.

    Also, beware that Reagent makes a big difference between a Clojure list vs a vector. You sometimes need to force an (eager) vector result into a seq with (seq ...) or a list via (apply list ...). You can also use the simple ->list function to emphasize what you are doing:

    (s/defn ->list :- [s/Any]
      "Coerce any sequential argument into a List."
      [arg :- [s/Any]]
      (apply list arg))