om - idiomatic way to compose components?

296 views
Skip to first unread message

Colin Yates

unread,
Nov 17, 2014, 6:49:31 AM11/17/14
to clojur...@googlegroups.com
Hi all,

I keep running into the really common use case (and seeing others running into as well) of composing components.

Quite simply, how is one supposed to compose components in om?

In my particular use case I have a Header component which displays a title and optionally a component describing a summary of what is being seen (which is more than simple text). I want to do something like:

(defn header [data owner]
(reify
om/IRender
(render [_]
(dom/div .....
(when optional-component) optional-component)))))

It works if I put the component into either the header state or opts (e.g.
(header data owner {:opts {:optional-component (om/build ...}}))

but neither feel idiomatic. If I had to chose the lesser of two evils I would chose :opts I guess.

I did consider multi-methods, but this didn't feel particularly nice either.

What am I missing as this doesn't seem to be an obscure use-case :)?

Colin Yates

unread,
Nov 17, 2014, 7:47:36 AM11/17/14
to clojur...@googlegroups.com
An alternative (from https://github.com/danielytics/ominate) is for the outer component to not actually be a component but be a factory (i.e. so it returns a function which returns the om component) which takes in the nested component as a symbol.

This has the slight downside that the factory must pass on the :opts (for example) to the nested component.

David Nolen

unread,
Nov 17, 2014, 7:52:44 AM11/17/14
to clojur...@googlegroups.com
I would leverage multimethods. However nothing prevents you from
composing components and passing them via props - this approach is
popular in React.

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

Jonas Enlund

unread,
Nov 17, 2014, 8:00:41 AM11/17/14
to clojur...@googlegroups.com
Please correct me if I'm wrong but wouldn't `(ominate ...)` mount a new component everytime its parent is rendered? It's my understanding that Om (and React?) does not really support anonymous components. Here's the implementation for reference:

https://github.com/danielytics/ominate/blob/master/src/ominate/core.cljs#L38-L79

Colin Yates

unread,
Nov 17, 2014, 8:02:18 AM11/17/14
to clojur...@googlegroups.com
Thanks David.

Daniel Kersten

unread,
Nov 17, 2014, 8:31:15 AM11/17/14
to clojur...@googlegroups.com

I haven't reviewed the ominate code in a while so would need to look at it again. Check the branch, however, as it was slightly more up to date.
As for unmounting and remounting, that used to be a bug yes but AFAIK this is no longer the case (since the multimethods fix)


--
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 clojurescript+unsubscribe@googlegroups.com.

Colin Yates

unread,
Nov 18, 2014, 5:24:43 AM11/18/14
to clojur...@googlegroups.com
The reason I have shied away from multimethods is that they aren't local to the current namespace (which is the point I guess :)), and I tend to end up 'using' each namespace which declares a defmethod.

For example, if I have a common/navbar which declares a defmulti say-hello, in
page-1 I defmethod say-hello .. "page-1". In page-2 I do the same. The problem is that I end up with a main namespace which uses both page-1 and page-2 so when page-1 calls common/navbar the defmethod in page-2 is the one that takes precedence.

I could pass in a qualifier to navbar which is used as part of the dispatching logic I guess (so page-1's qualifier is :page-1 etc.).

How do you solve this?

On Monday, 17 November 2014 12:52:44 UTC, David Nolen wrote:

Jamie Orchard-Hays

unread,
Nov 18, 2014, 10:15:20 AM11/18/14
to clojur...@googlegroups.com
Colin, is the optionally displayed component dependent on the data?

Colin Yates

unread,
Nov 18, 2014, 11:14:07 AM11/18/14
to clojur...@googlegroups.com
Hi Jamie,

It could (and probably will) be.

In my example, the Header component is the generic component but it might display a summary of the table being displayed elsewhere:

---------------------------------------
- HeaderComponent "Displaying 10/1000"
---------------------------------------
- TableComponent
---------------------------------------

where "Displaying 10/1000" is the summary of the same data TableComponent is using.

On the other hand, an "add screen" might look like:

---------------------------------------
- HeaderComponent "Please fill in X"
---------------------------------------
- FormComponent
---------------------------------------

and so on.

Other times the HeaderComponent won't have anything to display.

This is obviously just a hoicky example though.

Jamie Orchard-Hays

unread,
Nov 18, 2014, 4:28:40 PM11/18/14
to clojur...@googlegroups.com
If it's data-dependent, then I don't see any reason to pass components down the tree. You just look at the data and decide, based on the data, what you're showing in the Header.

It seems like you're dealing with different pages or different views. If needed, perhaps you have some local state you set on the component that contains the Header component. The container sets the Header's local state to whatever sort of summary is needed. As you and David mentioned, multi-methods might be useful in terms of simplifying the code.

Jamie

Colin Yates

unread,
Nov 19, 2014, 7:31:24 AM11/19/14
to clojur...@googlegroups.com
Hi Jamie, thanks for your comments.

I am not sure I understand your point about data dependency. To be clear, the HeaderComponent has no access to the data the optional-component is based on, and even if it did, giving the HeaderComponent the responsibility of switching means making it know far more than it should. Imagine the HeaderComponent lives in one of those awful "company wide frameworks" and the optional-component is project specific.

Yes, there is always multi-methods to fall back on, but as I mentioned they don't come for free and don't strike me as a good match for this problem. When I construct a particular instance of the HeaderComponent I know exactly which child component I want, there is no "strategy" here.

I think David's answer of passing it directly into the HeaderComponent as state is the way forward here. That still feels icky to me but that is almost certainly my lack of familiarity.

P.S. Does anybody else keep thinking they finally 'get it' and then the very next thing they do has them scurrying back to the om tutorials/react docs :).
P.P.S. Can library authors *please* pick more google-friendly search names ;).

Jamie Orchard-Hays

unread,
Nov 19, 2014, 2:12:46 PM11/19/14
to clojur...@googlegroups.com
I'd say if the Header component shouldn't know the data directly, then you've got to either pass the Summary component in, as David suggested, or pass the summary data in so the header can pick the correct summary component to use. Dunno how else I'd do it.

Om is not easy. But it is powerful :) I've done a lot of back and forth. My first attempt I knew I'd throw out and rebuild from scratch since building something with new materials means making beginner's mistakes. Other than some verbosity and some complexity (and a couple of contradictory fn names), I've been quite happy with Om. I'm in and out of it as I have been building a data entry tool with it that is used in a Ruby on Rails app. I had built a simpler tool in React, but there was no way in hell I was going to build the larger, more sophisticated tool in React. One of the huge wins from Om for my development is its cursors. It makes dealing with any sort of complex tree of data way easier than I found with Reagent, which I started with.

Jamie

Colin Yates

unread,
Nov 19, 2014, 4:05:56 PM11/19/14
to clojur...@googlegroups.com
Hi Jamie,

Yep, that is the conclusion I came to as well.

I know what you mean about the om simple/easy curve. In the past two weeks since I started with om I have almost daily run into something that seems harder than it needs to be, or at least more verbose, particularly around channels rather than callbacks and so on. Today must have been the 10th time I thought "om - really? Let me just remind myself about reagent" :)

Reply all
Reply to author
Forward
0 new messages