How to update a reagent atom filtering in a nested vector of maps in Clojure

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))

Upvotes: 0

Views: 399

Answers (3)

Walton Hoops
Walton Hoops

Reputation: 864

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.

Upvotes: 0

Rozar Fabien
Rozar Fabien

Reputation: 362

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)

Upvotes: 2

akond
akond

Reputation: 16035

(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))

Upvotes: 1

Related Questions