om (0.8alpha1) resolved cursor != contents of cursor

78 views
Skip to first unread message

Colin Yates

unread,
Nov 8, 2014, 8:44:51 AM11/8/14
to clojur...@googlegroups.com
Hi,

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.

jack james

unread,
Nov 8, 2014, 11:17:24 AM11/8/14
to clojur...@googlegroups.com
I don't think this is a bug. When you deref a cursor, you get the values stored in the underlying app-state atom. Here, you're storing a cursor in a component's local state. When you deref it later, you're still only going to get the values stored the the cursor's underlying app-state atom...

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:

https://www.refheap.com/92913

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:

https://www.refheap.com/92914

HTH

Colin Yates

unread,
Nov 8, 2014, 11:39:49 AM11/8/14
to clojur...@googlegroups.com

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.

jack james

unread,
Nov 8, 2014, 11:56:09 AM11/8/14
to clojur...@googlegroups.com
I try to avoid these types of generalizations. There's nothing inherently wrong with "decorating" a cursor, and it will work fine in many scenarios. But when you need app-state data + some other data in an event handler, you'll need a different approach, because deref will only return the underlying app-state values.

Colin Yates

unread,
Nov 8, 2014, 12:54:53 PM11/8/14
to clojur...@googlegroups.com
I see. Thanks again.
Reply all
Reply to author
Forward
0 new messages