I have found a bug, but it may well be (almost certainly is) in my understanding ;).
tldr; I decorate something from app-state and store as component-state. In rendering I see the decorated thing but in on-click when I resolve the cursor I see the undecorated thing.
I have a hierarchy in my app-state and a tree-component to render it. The tree component denormalises the tree so each node has an array of its parent's ids and descendant ids (for example). I then persist this decorated-tree as component state.
The problem is that when I reference the decorated tree in the on-click, I can see the cursor has access to the decorated tree but when I denormalise it I see the undecorated tree.
(As an aside, I originally tried it without component state and passed the decorated tree as app-state to the delegate component but that exhibited the same behaviour).
I am sure I have missed something, but I don't see what - is this a bug in om? I can by-pass this by simply storing everything in app-state in the denormalised view, but that is not ideal - different components want to render the same domain chunk differently (e.g. another component might show this tree as a flattened list).
The following code demonstrates the behaviour:
[code]
(defn the-component
[_ owner]
(reify
om/IDisplayName
(display-name [_] "Component")
om/IRenderState
(render-state [_ {:keys [node]}]
(let [{:keys [id text children meta]} node]
(js/console.log "NODE in rendering contains meta:"
(clj->js (keys node)))
(html
[:li
{:key id
:on-click
(fn [e]
;; prevent selection of the parent
(. e stopPropagation)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; the cursor node contains the "meta" key if you expand
;; into .value.root.arr[2]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(js/console.log "(cursor)NODE in on-click (check .value.root.arr[2]):")
(js/console.log node)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; however it has all gone pear shape here...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(js/console.log "(deref)NODE in on-click no longer has 'meta':"
(clj->js (keys @node))))}
"Click me"])))))
(defn tree
[{:keys [node] :as data} owner]
(reify
om/IDisplayName
(display-name [_] "Tree")
om/IInitState
(init-state [_]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; decorate the value of the cursor from app-state
;; but store locally
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
{:node (assoc node :meta {})})
om/IRenderState
(render-state [_ {:keys [node]}]
(om/build
the-component
nil
{:init-state {:node node}}))))
[/code]
(any and all comments welcome!)
Thanks.
A couple things:
1) Storing cursors in local state is an anti-pattern. It's one of the few patterns that we've seen clear guidance on, which is: don't do it.
2) In case this isn't clear: after you "decorate" a cursor with assoc, it's still a cursor (with some extra stuff tagging along). We can verify this with the "type" function:
That refheap also shows one of your options, which is to use om/value (like deref, but intended for use during the render phase) to convert your cursor to values, so that instead of storing a cursor in local state, you're storing values.
That would be better, but I still don't like it. You may be planning to update your application state in your event handler with om/transact! or om/update!, but those functions take cursors. So if you've already converted your cursor to values, you won't be able to use those functions. You would have to update app-state with another approach (callback, channel, or ref-cursor).
Also, remember the primary goal of react: to minimize the complexity of managing state:
http://facebook.github.io/react/docs/thinking-in-react.html
"Figure out what the absolute minimal representation of the state of your application needs to be and compute everything else you need on-demand."
In this case, you're taking data that's already stored in app-state, and storing it again in local state. I think you want to try avoid this wherever possible. Here's one possible approach that doesn't use local state at all:
HTH
James, thank you. That does help. My mental model of app-state being pure domain and view hierarchies effectively projecting their own structures is the thing to challenge.
The approach, if I may generalise is by all means project off app-state but keep it alongside (e.g. through opts as you show), rather than intermingled with?
Essentially, it all went wrong when I mutated the cursor itself.
Thanks James,
Colin
--
Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to a topic in the Google Groups "ClojureScript" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojurescript/SRqs211zf6E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at http://groups.google.com/group/clojurescript.