1

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.

  • Is the object in my vector not the same instance maybe, so removing it, will not affect the marker on the map?
  • Or do I have to do a different approach, when trying to execute sideeffected methods on objects in a vector?
  • Or did I miss something else entirely?
Jan
  • 2,747
  • 27
  • 35
  • 1
    I don't know how `r/atom` is different, but I assume you need still need to deref it (`@markers`) to iterate them. – cfrick Apr 05 '20 at 15:44
  • I think deref is just to tell Reagent to re-render my component on state change. That being said, I tried it nonetheless and it yields the same results: none. – Jan Apr 05 '20 at 15:56
  • You were right, after all. I had to deref in order to access the _content_ of the atom. Otherwise I just accessed the atom object. – Jan Apr 05 '20 at 19:50

1 Answers1

2

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))
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
  • I tried `(doseq #(.remove %) @markers)` but it won't compile: _doseq requires a vector for its binding_. I'm more and more convinced, I'm not storing a proper vector with `(defonce markers (r/atom []))`. – Jan Apr 05 '20 at 19:37
  • Sorry! Typed the answer too fast. Fixed now. – Alan Thompson Apr 05 '20 at 19:42
  • I can't believe, that did it. I was fighting with this little problem almost the whole day. And it was really just about accessing the atom properly in combination with the missing deref. Thanks! – Jan Apr 05 '20 at 19:47