3

I am updating state in one of my mutations, and a piece of it is not used by this component, but is by another one. When I do the mutate I see the that the app-state is updated in the repl, and if I cause the component to re-render for other reasons, it will show correctly, but I can not get the mutate to schedule a re-render of the second component. In the example below clicking on a button should decrement the value near the color name in the second list, but it does not.

There is some examples showing using :value [k k] in the mutate return, but those throw an error, must be out of date tutorials, as the current format is :value {:keys [...]}, so says the code and some tutorials . However I can't find any part of om.next actually USING :keys as a keyword that isn't a destructure operation (so not using :keys as an actual keyword, but it is a common word so I may have missed one somewhere)

In the repl I see this for the app-state:

=> (om/app-state reconciler)
#object [cljs.core.Atom {:val 
  {:tiles [[:tile/by-pos "a7"]
           [:tile/by-pos "a9"]
           [:tile/by-pos "a11"]],
   :inventory [[:inv/by-color "red"]
               [:inv/by-color "blue"]
               [:inv/by-color "green"]],
   :tile/by-pos {"a7" {:pos "a7", :color nil},
                 "a9" {:pos "a9", :color nil},
                 "a11" {:pos "a11", :color nil}},
   :inv/by-color {"red" {:color "red", :remaining 2},
                  "blue" {:color "blue", :remaining 1},
                  "green" {:color "green", :remaining 1}}}}]

What am I missing?

(ns omnexttest.core
  (:require [goog.dom :as gdom]
            [om.next :as om :refer-macros [defui]]
            [om.dom :as dom]))

(defmulti read om/dispatch)

(defmethod read :default
    [{:keys [state] :as env} key params]
      (let [st @state ]
            (if-let [[_ value] (find st key)]
                    {:value value}
                    {:value :not-found})))

(defmethod read :tiles
    [{:keys [state] :as env} key params]
     {:value (into [] (map #(get-in @state %) (get @state key))) })

(defmethod read :inventory
    [{:keys [state] :as env} key params]
     {:value (into [] (map #(get-in @state %) (get @state key))) })

(defmulti mutate om/dispatch)

(defmethod mutate 'draw/edit-edge
  [{:keys [state] :as env} _ {:keys [this pos color]}]
    {:value {:keys [[:inv/by-color color :remaining]]}
     :action (fn []  (do
               (swap! state assoc-in [:tile/by-pos pos :color] color )
               (swap! state update-in [:inv/by-color color :remaining] dec)))})

(defn hex-color
  [ this pos color ]
    (om/transact! this `[(draw/edit-edge ~{:this this :pos pos :color color})]))

(defui TileView
    static om/Ident
    (ident [this {:keys [pos]}] [:tile/by-pos pos])
    static om/IQuery
    (query [this] '[:pos :color])
    Object
    (render [this]
      (let [{:keys [pos color] :as props} (om/props this)]
          (dom/li nil
            (str pos " " color)
            (for [color ["red" "green" "blue"]]
              (dom/button #js { :onClick (fn [e] (hex-color this pos color)) }
                       color))))))

(def tile-view (om/factory TileView {:keyfn :pos}))

(defui InvView
    static om/Ident
    (ident [this {:keys [color]}] [:inv/by-color color])
    static om/IQuery
    (query [this] '[:color :remaining])
    Object
    (render [this]
      (let [{:keys [color remaining] :as props} (om/props this) ]
        (dom/li nil (str color " " remaining)))))

(def inv-view (om/factory InvView {:keyfn :color}))

(def app-state {
                      :tiles [{ :pos "a7"  :color nil }
                              { :pos "a9"  :color nil }
                              { :pos "a11" :color nil }
                              ]
                      :inventory [{ :color "red" :remaining 2}
                                  { :color "blue" :remaining 1}
                                  { :color "green" :remaining 1}]
                      })

(defui MapView
       static om/IQuery
       (query [this]
              [{:tiles (om/get-query TileView)}
               {:inventory (om/get-query InvView) }])
       Object
       (render [this]
               (let [tiles (-> this om/props :tiles)
                     inv (-> this om/props :inventory) ]
                (dom/div nil
                  (dom/ul nil
                   (mapv tile-view tiles))
                  (dom/ul nil
                   (mapv inv-view inv))))))

(def reconciler
  (om/reconciler
    {:state app-state
     :parser (om/parser {:read read :mutate mutate})}))

(om/add-root! reconciler
  MapView (gdom/getElement "map"))

(defn on-js-reload []
  ;; optionally touch your app-state to force rerendering depending on
  ;; your application
  ;; (swap! app-state update-in [:__figwheel_counter] inc)
)
snoonan
  • 160
  • 1
  • 5
  • I would look into your `app-state`. I can't see any idents there. – Chris Murphy Sep 06 '16 at 05:10
  • om.next did it for me, see dump of app-state in updated question. All the components have an Ident function that helps the translation. – snoonan Sep 08 '16 at 01:57
  • Good point. It matters whether you pass your app state as an atom or not, and there's a :normalized (or similar name) key that can be used. – Chris Murphy Sep 10 '16 at 19:32

1 Answers1

3

The this that is passed into om/transact! is important for re-rendering, so here if this was for a MapView component then all three components would be re-rendered. You can have the function in MapView (thus using MapView's this) but call it from TileView. In TileView's render you need something like this:

{:keys [click-cb-fn]} (om/get-computed this)

When you call om/transact! re-rendering is done down from the component you pass as first argument - this. Thus, to take this to its extreme, you'll never have re-rendering problems if all om/transacts!s are done from the root component, and all functions are passed down via computed props.

But you don't have to pass functions down. An alternative is to keep them at the same component where the firing button is, and instead pass down (again via computed props) the parent component's this. All that matters is what component the first argument to om/transact! is - call om/transact! from where ever you like.

Follow on reads are another thing to be considered when thinking about re-rendering, but not for the example you gave - they are best considered when the component you need to be re-rendered is in a different subbranch of the render tree, where using a common root's this would not be practical.

Another thing to note is that a mutate's value is 'just for documentation'. So whatever you put there will have no effect.

Chris Murphy
  • 6,411
  • 1
  • 24
  • 42
  • OK, I understand that I need to mention the (smallest) component that holds all the things to be re-rendered, but does that not violate the intent to have components that are not tied to the global scope? Why have the :keys field in the mutator then, if it can not be used to say what else is modified so might need re-rendering, and how can a component be reusable if it needs to know the whole tree down to it in order to call the transact on the higher containing component. – snoonan Sep 10 '16 at 16:34
  • It makes more sense to me to have the :value be meaningful than to have the follow on reads do it. The caller of om/transact! is less likely to know what the modification is going to effect then the implementation of the mutation. – snoonan Sep 12 '16 at 18:50
  • Unrelated, but I can't find it elsewhere? Where is a good place to hang out to find people with om.next knowledge? IRC somewhere? slack? forum? – snoonan Sep 12 '16 at 18:51
  • Yes sure. There's an Om slack group that is always talking about Om Next (not so much Om anymore). I would also recommend the Untangled slack group. Untangled is a framework build on top of Om Next. – Chris Murphy Sep 12 '16 at 23:31
  • I have a question that is very similar to this one. Hope you could look at it and answer. http://stackoverflow.com/questions/42733140/om-next-component-state-change-does-not-always-trigger-re-render – Khoi Mar 11 '17 at 08:39
  • Also how do I join the Om slack group? I did a search on Google but found nothing. – Khoi Mar 11 '17 at 08:44