jmargolisvt
jmargolisvt

Reputation: 6088

How to update Reagent vector in an atom

I've got a Reagent atom:

(defonce order (r/atom {:firstName "" :lastName "" :toppings [] }))

I want to add toppings to the :toppings vector. I have tried many variations on:

(swap! (:toppings order) conj "Pepperoni") which gives me: Uncaught Error: No protocol method ISwap.-swap! defined for type null:

(swap! order :toppings "Pepperoni") sort of works, but just updates order, not the :toppings vector. When I deref order, I just get the latest value.

What is the right way to add (and remove) values to my :toppings vector?

Upvotes: 2

Views: 1066

Answers (3)

grandinero
grandinero

Reputation: 1225

Just to explain a little more, when you do (swap! (:toppings order) ...), you are retrieving the :toppings key from order, which would make sense if it were a map, but it's an atom, so (:toppings order) returns nil.

The first argument to swap! should always be an atom (Reagent atoms work the same way). The second argument should be a function which takes the content of the atom as its first argument. Then, you can optionally provide more arguments that will be passed to the function argument.

Instead of minhtuannguyen's answer, you could do the following:

(swap! order
  (fn a [m]
    (update m :toppings
      (fn b [t]
        (conj t "Pepperoni")))))

fn a receives the map inside the atom, binds it to m, then updates it and returns a new map, which becomes the new value of the atom.

If you wanted to, you could redefine fn a to take a second argument:

(swap! order
  (fn a [m the-key]
    (update m the-key
      (fn b [t]
        (conj t "Pepperoni"))))
  :toppings)

:toppings is now being passed as the second argument to fn a, and then passed to update inside of fn a. We could do the same to the third argument to update:

(swap! order
  (fn a [m the-key the-fn]
    (update m the-key the-fn))
  :toppings
  (fn b [t]
    (conj t "Pepperoni")))

Now update has the same signature as fn a, so we no longer need fn a at all. We can simply provide update directly in place of fn a:

(swap! order update :toppings
  (fn b [t]
    (conj t "Pepperoni")))

But we can keep going, because update also accepts more arguments which it then passes to the function provided to it. We could rewrite fn b to take another argument:

(swap! order update :toppings
  (fn b [t the-topping]
    (conj t the-topping))
  "Pepperoni"))

Once again, conj has the same signature as fn b, so fn b is redundant, and we can just use conj in its place:

(swap! order update :toppings conj "Pepperoni")

Thus, we end up with minhtuannguyen's answer.

Upvotes: 6

user2609980
user2609980

Reputation: 10514

I would turn toppings into a set. I don't think you want duplicate toppings in the collection, so a set is appropriate:

(defonce order (r/atom {:first-name "" :last-name "" :toppings #{}})) ; #{} instead of []

Then you can still conj as stated in the other answer:

(swap! order update :toppings conj "Pepperoni")

but you can also disj:

(swap! order update :toppings disj "Pepperoni")

Upvotes: 5

Minh Tuan Nguyen
Minh Tuan Nguyen

Reputation: 1054

You can update toppings with:

(swap! order update :toppings conj "Pepperoni")

Upvotes: 4

Related Questions