Search code examples
clojurescriptreagentre-frame

re-frame: adding a new map element in nested vector


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 .


Solution

  • 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.