Using go block inside Om component

506 views
Skip to first unread message

Andrew Stoeckley

unread,
Jun 22, 2014, 8:24:34 AM6/22/14
to clojur...@googlegroups.com
I was reading this thread: https://groups.google.com/forum/#!msg/clojurescript/V5J1Rf0k84M/JgspX_AyoGgJ

It was suggested that all state changes to the Om atom should occur within an Om component. Currently I am swap!ing outside Om.

A lot of my state changes are the result of an infinite <! loop inside a go block, which is reading a websocket. What happens if I put this inside an Om component, and where is the best place to do so? IWillMount?

Since the block is asynchronous, couldn't the Om protocol (whichever one is best) return before the go block actually does anything? Or what happens when the go block does something at a different time than the Om protocol is getting called?

Or perhaps more likely there is a better way. Any suggestions on good approach here are appreciated.

Dave Della Costa

unread,
Jun 22, 2014, 9:20:15 AM6/22/14
to clojur...@googlegroups.com
Hi Andrew,

We ended up doing exactly the sort of thing you describe. We have a go
block inside IWillMount, listening for messages from the server (in our
case via browserchannel, but exact same idea). Inside this go block we
apply updates from our server by calling transact! on the app data.

> Since the block is asynchronous, couldn't the Om protocol (whichever
> one is best) return before the go block actually does anything? Or
> what happens when the go block does something at a different time
> than the Om protocol is getting called?

componentWillMount (a.k.a. IWillMount in Om) is called once at the very
beginning of the React lifecycle
(http://facebook.github.io/react/docs/component-specs.html#mounting-componentwillmount),
so it's not going to cause the kind of strange behavior you're concerned
about. If a message comes in and transact! gets called, an update will
be scheduled and rendering will happening as expected.

DD

Gary Trakhman

unread,
Jun 22, 2014, 9:24:32 AM6/22/14
to clojur...@googlegroups.com
Let's also not forget that JS is single-threaded.  I'm guessing all the components will be recursively mounted before the contents of the go block can do anything at all.


--
Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to the Google Groups "ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, 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.

Andrew Stoeckley

unread,
Jun 22, 2014, 9:28:17 AM6/22/14
to clojur...@googlegroups.com
Ok thanks guys, that provides some confidence. That other thread also
mentioned having an invisible Om component do the listening. Not sure
I understand the point of that; why not just add the go block to
anything already in the IWillMount of your root Om component? If you
were to make it invisible, just put an empty om/div on there with no
contents? (since a component must be returned)
> 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/DHJvcGey8Sc/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to

William Sommers

unread,
Jun 22, 2014, 9:31:27 AM6/22/14
to clojur...@googlegroups.com
That is the case that I've seen most often Gary.

Dave Della Costa

unread,
Jun 22, 2014, 9:51:18 AM6/22/14
to clojur...@googlegroups.com
Sure, the main reason for us is to keep things modular. It makes it
simpler to restructure things, since it's just another component.

We wrap our root component in this "invisible" component, so it just
calls build on the root component itself (that is, the invisible data
listener component becomes the actual root component).

Daniel Kersten

unread,
Jun 22, 2014, 9:59:05 AM6/22/14
to clojur...@googlegroups.com
I also use an "invisible" management component to handle updates from outside of Om. Works great.


If you wrap your root with this component, everything should work just fine, but if you create go blocks inside components that can be unmounted, you have to remember to kill the go block from IWillUnmount or it will stick around after the component is unmounted - if the component gets mounted again, you will have two go blocks running! And while its unmounted, accessing owner will cause problems.

The way to fix this (other than avoiding go blocks in components that might unmount) is to close the channel being listened on in IWillUnmount (if this component owns the channel) or use core.async/alt to listen on a "kill" channel as well as the actual channel if this component does not own the channel, then simply close the kill channel in IWillUnmount. This way go blocks only exist while the component is mounted.

Daniel Kersten

unread,
Jun 22, 2014, 10:00:57 AM6/22/14
to clojur...@googlegroups.com
PS: 

My invisible component is a container for the actual root - so I don't return an empty div, I return (om/build real-root-component app-state options-passed-to-root)

Andrew Stoeckley

unread,
Jun 22, 2014, 10:07:18 AM6/22/14
to clojurescript
Thanks Daniel and everyone; this has been quite helpful.

Paul Cowan

unread,
Jun 22, 2014, 10:18:01 AM6/22/14
to clojur...@googlegroups.com
I have a go block in IWillMount here that is called periodically on a timeout function.

Do I run the risk of having multiple go blocks running?  Should I be killing the channel?

Cheers

Paul Cowan

Cutting-Edge Solutions (Scotland)

Andrew Stoeckley

unread,
Jun 22, 2014, 10:29:45 AM6/22/14
to clojur...@googlegroups.com
Actually this raises a question: if IWillMount runs repeatedly,
wouldn't the go block inside it get generated every time? Normally I'd
have a single go block living outside the Om stuff, and it listens;
but you'd have multiple go blocks created if they were coded inside
IWillMount, wouldn't you?

Andrew Stoeckley

unread,
Jun 22, 2014, 10:52:38 AM6/22/14
to clojurescript
I think I didn't understand entirely what constitutes a mount and
unmount in Om; is an unmount similar to no longer calling build on a
component that was previously getting built and rendered? i.e. a
parent component decides to not call build any more on a child,
thereby removing it from the screen, thus this would be an unmount?

William Sommers

unread,
Jun 22, 2014, 11:40:02 AM6/22/14
to clojur...@googlegroups.com
Hey Andrew,

I put up a small thing to take a look at mounting and unmounting. It is up here. https://github.com/Will-Sommers/mount-unmount-test

Your intuition is correct. When a parent component decides not to previously mounted child component then it is unmounted. I also put up a small example where a parent component attempts to build a child component when no data is available to see if will-unmount is called. After looking at the dom it is obvious that the component is still rendered but without the data and therefore does not unmount.

Take a look.

Daniel Kersten

unread,
Jun 22, 2014, 11:59:04 AM6/22/14
to clojur...@googlegroups.com
Paul, no it looks like your good.

Basically, you only run the risk if the component in which the go block is defined can be unmounted. A lot of components will never be unmounted during the normal execution of the app. Also, I ran code that creates go blocks in IWillMount for about a month before even realising that this was happening, so you may not even see any noticeable effects - but eventually I hit a bug that was eliminated by killing stale go blocks.

So how do you know if a component can be unmounted? Look at how it gets built. If you can trace it all the way back to the root without any conditional rendering, as in Paul's code, then you're safe (as far as I can tell, at least). In Paul's code, start-app is the root component, which renders world-view, which creates the go block. So the only function that could cause world-view to be unmounted is start-app, but its render function always renders world-view, hence no unmounting.

In my own experience digging through the code and testing, I've found that (and David has confirmed this in a comment on the issue tracker) components get unmounted if the function which returns the component (ie the one that calls reify and gets passed to build) is different. Some examples where components get unmounted:

(om/build (get components index) ...) ; If you change index, the previous component gets unmounted before the new component gets mounted

(om/build (if foo component-1 component-2) ...) ; If you toggle foo from true to false, then component-1 gets unmounted and component-2 gets mounted

(dom/div nil (when foo (om/build component ...))) ; If you toggle foo from true to false, then component gets unmounted

;; Don't do this! ;-)
(om/build (fn [props owner] (reify om/IRender (render [_] (dom/div nil "Hello")))) props) ; This component gets unmounted every time the parent is rendered! Because (fn ...) is DIFFERENT every time! Oops! Also be careful of helper functions that might create components that get recreated every time!

So if you have code that's like this, those components will want to clean up after themselves in IWillUnmount.

Hope this helps!

Daniel Kersten

unread,
Jun 22, 2014, 12:00:45 PM6/22/14
to clojur...@googlegroups.com
Andrew, yes. 

From my understanding, it works like this:

Om renders a tree of nodes. React diffs this tree with the previous version that it rendered to the DOM. Any nodes that existed in the previous version, but not in the current get unmounted.


Andrew Stoeckley

unread,
Jun 22, 2014, 5:35:56 PM6/22/14
to clojur...@googlegroups.com
I guess this is obviously why putting a go block in the topmost level om/root is wise, since there would definitely not be internal logic which could unmount it, as it has no Om parent. 

Sent from my iPad

Daniel Kersten

unread,
Jul 8, 2014, 2:47:51 PM7/8/14
to clojur...@googlegroups.com

Your approach is very similar to what I'm currently doing. I have an invisible root component that manages pushing external changes to the state. I also use tx-listen to do the opposite: push om-initiated changes to the server (similar to om-sync).

On 8 Jul 2014 19:28, "Andrew Stoeckley" <andr...@gmail.com> wrote:
Actually in hindsight the draining wouldn't be necessary inside an
infinite go loop such as a while-true inside the Om component.

On Tue, Jul 8, 2014 at 11:23 PM, Andrew Stoeckley <andr...@gmail.com> wrote:
> I'd be curious what you all think of this strategy for using a channel
> for updating the Om atom inside a component. I have several different
> places in the app that need to notify Om of new data. It can come from
> anywhere based on a variety of things, so I've just created a single
> channel, and onto this channel is placed a vector of args like [cursor
> korks v] for passing to om/transact!. When I read this channel in a go
> block inside an Om root component, the vector is pulled off and an
> (apply om/transact! [cursor korks v]) is processed for whatever was
> put on the vector. This allows a single entry point to the actual
> updating of the Om atom via the proper transact! while still letting
> any area of the app dump state changes onto the channel.
>
> I'm still coding up everything, and of course one of the obvious
> issues is that many items may be placed on the channel before the next
> read (though unlikely), thus it becomes necessary in the go block to
> drain the entire channel and use up all its values at once so all
> pending updates are processed, for which I'm using a more meaningful
> variation of this go-loop-alt approach:
> https://gist.github.com/hellofunk/b32c8e0d267ada0cc396
>
> As I'm new to both Om and core.async, I'd enjoy hearing any obvious
> alternatives to this approach or just a vote of confidence that it's a
> decent idea.
Reply all
Reply to author
Forward
Message has been deleted
Message has been deleted
0 new messages