0

I have this structure (ordered map) in my "db" with the keyword ":questions":

{:33 {:question "one", :id 33, :answers [{:id 22, :question_id 33, :answer "one", :correct false}
                                              {:id 4,  :question_id 33, :answer "two", :correct false}]}},
{:39 {:question "two", :id 39, :answers []}},
{:41 {:question "three", :id 41, :answers [{:id 29, :question_id 41, :answer "one", :correct false}
                                                {:id 35, :question_id 41, :answer "two", :correct true}]}} 

I can add a new question in the event handler "re-frame/reg-event-db" adding:

(assoc-in db [:questions (:id response)] new-map-stuff)

but I don't know how to add a map in the ":answers" key. Besides, I'm afraid that I'm rendering all the questions every time I'm adding a new answer.

I read about the "path" interceptor (kind of "update-in"), but I can't find an example about how to use it .

aarkerio
  • 2,183
  • 2
  • 20
  • 34

1 Answers1

1

You do it just like in plain clojure, using update-in. First start at the Clojure CheatSheet:

http://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html

or the ClojureScript version: http://cljs.info

Look at the docs for update-in: https://clojuredocs.org/clojure.core/update-in

You need 2 componenents

  1. A path to navigate the the site of the mutation
  2. A function to perform the mutation given the point from (1).

You didn't fully specify your data. I'm assuming it looks like this:

(def db {:questions [
           {:33 {:question "one", :id 33,
                 :answers  [{:id 22, :question_id 33, :answer "one", :correct false}
                            {:id  4, :question_id 33, :answer "two", :correct false}]}},
           {:39 {:question "two", :id 39, :answers []}},
           {:41 {:question "three", :id 41,
                 :answers  [{:id 29, :question_id 41, :answer "one", :correct false}
                            {:id 35, :question_id 41, :answer "two", :correct true}]}}]})

I'll make up a new answer map to add:

(def new-answer {:id 42, :question_id 442, :answer "The Ultimate", :correct false})

This code shows the process in pieces.

  (let [submap       (get-in db [:questions 0 :33 :answers])
        modified     (conj submap new-answer)
        final-answer (update-in db [:questions 0 :33 :answers] conj new-answer)   ]

First the navigation to get submap:

submap => 
[{:id 22, :question_id 33, :answer "one", :correct false}
 {:id  4, :question_id 33, :answer "two", :correct false}]

And the way you add the new answer:

modified => 
[{:id 22, :question_id  33, :answer "one", :correct false}
 {:id  4, :question_id  33, :answer "two", :correct false}
 {:id 42, :question_id 442, :answer "The Ultimate", :correct false}]

And putting it all together into one operation:

final-answer => 
{:questions
 [{:33
   {:question "one",
    :id 33,
    :answers
    [{:id 22, :question_id  33, :answer "one", :correct false}
     {:id  4, :question_id  33, :answer "two", :correct false}
     {:id 42, :question_id 442, :answer "The Ultimate", :correct false}]}}
  {:39 {:question "two", :id 39, :answers []}}
  {:41
   {:question "three",
    :id 41,
    :answers
    [{:id 29, :question_id 41, :answer "one", :correct false}
     {:id 35, :question_id 41, :answer "two", :correct true}]}}]}

All of this works identically in ClojureScript as in Clojure.

Don't worry about using the path interceptor. I think it confuses things more than it helps. And don't worry about render speed unless:

  1. You have it working and tested and know it is right.
  2. You have measured the render speed and can document that there is a problem and the amount you need to speed up.
  3. For long lists of things look at the key metadata you can add to each list element for Reagent. See: Reagent React Clojurescript Warning: Every element in a seq should have a unique :key

Update

Note that submap and modified are shown only for demonstration purposes to show pieces of how update-in works. The update-in expression is the only thing you would actually include in your code. Also, note that the update-in only uses db and new-answer.

Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
  • Thanks a lot! Just to notice, in the dotest block the final call is: final-answer (update-in db [:questions 0 :33 :answers] conj **modified**) right? – aarkerio Oct 11 '18 at 17:07
  • 1
    No. We are using `conj` to append `new-answer` onto the end of the existing `:answers` array. Study `update` & `update-in`. The path that finds `modified` silently inserts a reference to the destination as the first arg to the function `conj`. The `new-answer` becomes the 2nd arg to `conj`. The result of `conj` is then stitched back into place deep in the hierarchy. – Alan Thompson Oct 11 '18 at 17:19