0

Let's say I have a reagent atom with a vector of maps like this:

(def my-atom (reagent/atom {:id 256 
                             :name "some name"
                             :lines [{:code "ab43" :name "first nested name" :quantity 4}
                                     {:code "bc22" :name "second nested name" :quantity 1}
                                     {:code "lu32" :name "third nested name" :quantity 1}}] }))

How can I update the value of a key :quantity at a certain vector nested index, for example: update line with code "bc22" to 10 quantity.

This need to filter to get the index of vector, but haven't the index because filter by "code":

 (swap! my-atom assoc-in [:lines 1 :quantity] 10)

I can find with filter, but I can't swap! quantity:

(->> (:lines @my-atom)
     (filter #(= (:code %) "bc22")
     first))
akond
  • 15,865
  • 4
  • 35
  • 55

3 Answers3

2

You can stick with the use of assoc-in but to do so, you have to retrieve the index associated to a given code from the vector of the :lines field in some way.

For example, I would a helper function:

(defn code->index [data code]
  (->> data
       :lines
       (map-indexed (fn [i v] [i v]))
       (filter (fn [[_ v]] (= (:code v) code)))
       ffirst))
;; (code->index @my-atom "bc22")
;; => 1

And then use it in the swap:

(swap! my-atom assoc-in [:lines (code->index @my-atom "bc22") :quantity] 10)
Rozar Fabien
  • 352
  • 2
  • 9
  • solves the problem, but is it the best way? I tried with `update-in` and `update`, but I don't swap! the atom with assoc. Another clojure function? – Diego Peñalver Montero Aug 09 '20 at 16:42
  • From my side, the `swap!` also works with `update-in`: `(swap! my-atom update-in [:lines (code->index @my-atom "bc22") :quantity] inc)` When you use `assoc` like functions, you don't use the current value contained by the atom. With `update` like function, you pass as last argument a function which will modify the given value. I don't know if it's the canonical way to do it, but I find it pretty straightforward. – Rozar Fabien Aug 09 '20 at 20:33
1
(require
    '[com.rpl.specter :as s])

(let [*a (atom {:id    256
                   :name  "some name"
                   :lines [{:code "ab43" :name "first nested name" :quantity 4}
                           {:code "bc22" :name "second nested name" :quantity 1}
                           {:code "lu32" :name "third nested name" :quantity 1}]})]
        (s/setval [s/ATOM :lines s/ALL #(-> % :code (= "bc22")) :quantity] 10 *a))
akond
  • 15,865
  • 4
  • 35
  • 55
0

You've got options here. You could look up the index of the item, you could map over the list, updating only the item your interested in.

Depending on the specifics of the situation, you could also look at either storing the index of the element when the component is rendered, or instead build a set of cursors which are passed to your component. In that case you simply update he cursor like you would an atom, at it handles updating the backing atom efficiently.

Personally, I look at this and wonder if you are using the correct data structure in the first place. It seems probable that code is a natural key here, especially since you are looking to update a line based on it. Perhaps a map with code as the key and the full line as the value would make more sense. This also makes certain undesirable situations impossible (e.x. multiple lines with the same code). Of course you'd lose ordering (unless you re-established it somehow), which may or may not be an issue.

Walton Hoops
  • 864
  • 4
  • 14