re-frame - sanity check for re-usable components

205 views
Skip to first unread message

Colin Yates

unread,
Mar 27, 2015, 8:54:16 AM3/27/15
to clojur...@googlegroups.com
In re-frame event dispatching is handled by (dispatch [:discriminator detail]). A corresponding (register-handler :discriminator (fn [db [_ detail]]) then reacts to that dispatched event.

My question is how are people managing this with re-usable components? For example, I have a tree and when selecting a node in that tree something should happen. But this is where it gets all polymorphic as _what_ happens depends on the client who instantiated the tree. I can see the following ways forward:

- tree is configured with a 'context' key which is combined with the discriminator so rather than the tree emitting :node-selected it emits :consumer-a-node-selected. Consumer a can then handle consumer-a-node-selected and consumer b can handle (go on, guess) consumer-b-node-selected
- a variation on the above involving writing your own dispatching logic...
- tree doesn't use dispatch as the event bus, rather it takes in an instance of a Protocol:
IRespondToTree
(on-node-select [this node])
- tree is parameterised with a map of fns {:node-selected-fn ...} etc.

How would you all handle it? (I am leaning towards the first one).

Jamie Orchard-Hays

unread,
Mar 27, 2015, 9:39:37 AM3/27/15
to clojur...@googlegroups.com
Does it make sense to pass the reusable component's context as an argument to it? ie,

(defn ReusableComponent [some-context] .... )

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

Colin Yates

unread,
Mar 27, 2015, 9:41:40 AM3/27/15
to clojur...@googlegroups.com
It sure does, but _what_ would you put in that argument ;).
Message has been deleted

Colin Yates

unread,
Mar 27, 2015, 9:48:58 AM3/27/15
to clojur...@googlegroups.com
That does make things more ignorant which is very nice. Not sure why I
didn't see that originally given I usually jump all over coupling.
Guess I need some more sleep ;)

Thanks Mike.

On 27 March 2015 at 13:43, Jane Dampney <janed...@gmail.com> wrote:
> None of our reusable components know about re-frame. None of them dispatch directly. That's the job of the application specific code using both re-frame and re-com (our reusable component library).
>
> Instead, our (re-com) reusable components are given (by the app) on-click callbacks, or on-change callbacks, etc. And these callbacks are often called back with an identifier (what got clicked, or what changed).
>
> When the app creates the component, it supplies these callbacks, and organises for the callback to do the dispatch. The app creates the glue between the libraries.
>
> Here's an example (we use named parameters):
>
> [radio-button
> :model some-subscription-val ;; the currently selected value in the button group
> :value "green"
> :label "make it green"
> :on-change (fn [val] (dispatch [:set-colour val])]
>
> Notice that the reusable component “radio-button” doesn't dispatch itself. It just calls back to a passed callback, which the app code creates (and which happens to dispatch). So the app code itself is the glue between the reusable components and re-frame.
>
> In your case, with a tree, I'd imagine something like this sketch:
>
> Your reusable component requires the tree be specified in a certain way. Eg: Each node has an "id", "a label" and, optionally, "children".
>
> (def tree-data
> [ {:id 1 :label "item 1" :children [{:id "me" :label "me"}
> {:id "them" :label "not me"}
> ]}
> {:id 2 :label "item 2" :children [{:id :sub1 :label "sub1"}
> {:id :sub2 :label "sub2"}
> ]}]):
>
>
> Then you'd have a tree component which an app can use like this:
>
> [tree
> :nodes tree-data
> :on-click (fn [id] (dispatch [:node-clicked id]))]
>
> Notice how the component calls back with the :id of the clicked item. (Supplied by you in the tree-data structure).
>
> --
> Mike

Khalid Jebbari

unread,
Mar 27, 2015, 9:58:17 AM3/27/15
to clojur...@googlegroups.com
Not sure I'm going to answer the question directly, since I've never used re-frame, Reagent or Om. I'm just an experienced React.js developer VERY interested with Clojure(Script).

If you want reusable components, whatever wrapper you use around React, here's the plan.

You should create 2 types of components : dumb and smart. Dumb components do 2 simple things : display stuff and handle input/events. The way they handle should be agnostic to any kind of library and should use only language-level feature. Functions. So the dumb component use function it's been passed and call it with the input/event. The smart components wrap dumb components and connect to the outside world with whatever your stack uses (channels, events, ratoms, what not).

This way your dumb components are always reusable, whatever stack/project they're incorporated in. They can also be displayed in a simple page for your graphics or HTML/CSS team to check their look. The smart components handle whatever logic you want to put in them. So from a stack/page/project to another, only the smart components change, no the dumb ones. This keep UI consistent and separate concerns.

Hope it's clear and helpful.

Daniel Kersten

unread,
Mar 27, 2015, 10:03:58 AM3/27/15
to clojur...@googlegroups.com
I'm currently tinkering with an approach where my code does not dispatch re-frame events at all, but rather uses my own emit function to emit signals[1]. I then, separately, have a signal->event map, so the (single global) signal handler then maps signals to events and dispatches these events to re-frame.

What this gives me is that the components don't have to know what the (DOM) event callbacks actually do. Eg, instead of clicking the "add to list" button dispatching an [:add-to-list ...] event, it triggers an :add-to-list/clicked event and its up to the application to map this to the [:add-to-list ...] event. This way this component can be reused elsewhere by mapping the signal to whatever it makes sense for it to be mapped to.

This indirection also means you can map one signal to trigger multiple re-frame events, although I haven't played with this enough yet to know if that is desirable or not.

I like to think of re-frame events and handlers as the applications actions or capabilities - that is, the things that you can do. That way it makes sense that there is a 1:1 relationship between events and event handlers (eg only one handler can handle [:add-to-list ...]). That way, components emit "this thing just happened" signals which are mapped to "this outcome should be performed" actions/capabilities.

Obviously reusable components can't always be made in isolation of the actions that they trigger, especially if they need to maintain transient/intermediary state. For this I like to define the component specific handlers as namespaced keywords, eg :my-list-editor/add-to-list

I'm still undecided if such events should be dispatched directly or go through the signal indirection.

Some final thing to note about the approach I'm investigating:
1. My signal->event mapper  can take an optional (pure) function to convert signal parameters to whatever data the handler expects.
2. I like to keep my handler functions pure too and do any side-effects (eg server comm or dispatching new events/emitting signals) in middleware.

Hope that gives you some ideas.

[1] I haven't got the terminology down yet :) I'm calling them signals for now because they're kinda like Qt's signals & slots, but I dunno if they really fit into the definition of signals from the FRP world.

On Fri, 27 Mar 2015 at 13:58 Khalid Jebbari <khalid....@gmail.com> wrote:
On Friday, March 27, 2015 at 2:39:37 PM UTC+1, Jamie Orchard-Hays wrote:
> Does it make sense to pass the reusable component's context as an argument to it? ie,
>
> (defn ReusableComponent [some-context] .... )
>
> Jamie
>
> On Mar 27, 2015, at 8:54 AM, Colin Yates <colin...@gmail.com> wrote:
>
> > In re-frame event dispatching is handled by (dispatch [:discriminator detail]). A corresponding (register-handler :discriminator (fn [db [_ detail]]) then reacts to that dispatched event.
> >
> > My question is how are people managing this with re-usable components? For example, I have a tree and when selecting a node in that tree something should happen. But this is where it gets all polymorphic as _what_ happens depends on the client who instantiated the tree. I can see the following ways forward:
> >
> > - tree is configured with a 'context' key which is combined with the discriminator so rather than the tree emitting :node-selected it emits :consumer-a-node-selected. Consumer a can then handle consumer-a-node-selected and consumer b can handle (go on, guess) consumer-b-node-selected
> > - a variation on the above involving writing your own dispatching logic...
> > - tree doesn't use dispatch as the event bus, rather it takes in an instance of a Protocol:
> >  IRespondToTree
> >  (on-node-select [this node])
> > - tree is parameterised with a map of fns {:node-selected-fn ...} etc.
> >
> > How would you all handle it? (I am leaning towards the first one).
> >
> > --
> > 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.

> > To post to this group, send email to clojur...@googlegroups.com.
> > Visit this group at http://groups.google.com/group/clojurescript.

Not sure I'm going to answer the question directly, since I've never used re-frame, Reagent or Om. I'm just an experienced React.js developer VERY interested with Clojure(Script).

If you want reusable components, whatever wrapper you use around React, here's the plan.

You should create 2 types of components : dumb and smart. Dumb components do 2 simple things : display stuff and handle input/events. The way they handle should be agnostic to any kind of library and should use only language-level feature. Functions. So the dumb component use function it's been passed and call it with the input/event. The smart components wrap dumb components and connect to the outside world with whatever your stack uses (channels, events, ratoms, what not).

This way your dumb components are always reusable, whatever stack/project they're incorporated in. They can also be displayed in a simple page for your graphics or HTML/CSS team to check their look. The smart components handle whatever logic you want to put in them. So from a stack/page/project to another, only the smart components change, no the dumb ones. This keep UI consistent and separate concerns.

Hope it's clear and helpful.

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

Mike Thompson

unread,
Mar 27, 2015, 10:12:17 AM3/27/15
to clojur...@googlegroups.com
I'm not sure how much this perspective applies to the Clojurescript wrappings, but here are further references:
https://medium.com/@learnreact/container-components-c0e67432e005
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

--
Mike

Colin Yates

unread,
Mar 27, 2015, 10:12:45 AM3/27/15
to clojur...@googlegroups.com
Thanks both - it does. My brain wasn't really engaged when I asked the
question. The salient point is that there are levels/layers in play
here.

I like your idea that dispatched events are application level events..

I might get shot for saying this, but I actually don't care
particularly about re-usable components as IME the effort required to
get them truly re-usable is less than the churn required to adapt them
to a new framework - maybe I am just not being strict/clean enough...
I do care very much about increasing ignorance/decoupling everywhere.

Unfortunately that ignorance is mostly in my head today it seems :).
>> > > an email to clojurescrip...@googlegroups.com.
>> 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.
>
> --
> 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.

Khalid Jebbari

unread,
Mar 27, 2015, 10:18:44 AM3/27/15
to clojur...@googlegroups.com
If you're more interesed into the pattern I described, you can read more here : https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0. This is really where the React.js community is headed.

Marc Fawzi

unread,
Mar 27, 2015, 11:19:44 AM3/27/15
to clojur...@googlegroups.com
Khalid,

I read about that on some blog post (dumb and smart component) but I'm afraid that dumb components in this case will be truly dumb and offer little benefit for them to be shared (what is worth reusing/sharing is the behavior, markup and styles, not the markup and styles alone) 

To make sure I'm getting what you're proposing, are you saying dumb components don't encapsulate behavior? I think the same question applies in case of what Jane describes.

Jamie Orchard-Hays

unread,
Mar 27, 2015, 11:45:27 AM3/27/15
to clojur...@googlegroups.com
Good articles. Thanks, Mike. In my apps I think of them as "generic" and "specific", or something like that. IOW, I want some generic views that are dumb and know nothing about the app and can be used where ever I need them. They get composed into specific views.

Jamie

Khalid Jebbari

unread,
Mar 27, 2015, 1:25:40 PM3/27/15
to clojur...@googlegroups.com
Yes, dumb components don't encapsulate beahvior. Indeed if you generic behaviour you need as someone proposed to push a generic event, and the app handles it specifically.

Khalid aka DjebbZ
@Dj3bbZ

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/WOGdUY79Xv4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojurescrip...@googlegroups.com.

Marc Fawzi

unread,
Mar 27, 2015, 1:43:12 PM3/27/15
to clojur...@googlegroups.com
Yeah but then what are you really reusing? I have components that have sophisticated behavior... sharing just the markup and styles is of limited value since what I want to share in effect is the smart components, which is why I had settled on components encapsulating behavior as a pattern... works better for me to share the whole component (with its behavior) rather than just the dumb part....

Daniel Kersten

unread,
Mar 27, 2015, 1:45:27 PM3/27/15
to clojur...@googlegroups.com
Marc, can you tell us a little more about the approach your taking?

Khalid Jebbari

unread,
Mar 27, 2015, 1:58:22 PM3/27/15
to clojur...@googlegroups.com
If this approach suits you better, fine. Remember, this approach is coming from the React.js world, where everything is mutable and where there's no committment to a particular way of passing data/events. For my current projects, we indeed encapsulated both markup and behaviour, but it was hard sometimes to make it reusable without changing/hacking it. We didn't use a global method for sharing state like Flux, so it may explain why.



Khalid aka DjebbZ
@Dj3bbZ

Marc Fawzi

unread,
Mar 27, 2015, 3:17:32 PM3/27/15
to clojur...@googlegroups.com

You can have your cake and eat it. 

Will present the complete approach on the next Reagent meetup in SF, and while we are talking about having a presentation about re-frame port of ANgular's phonecat example, the reusable "smart" components presentation will be based on RAW Reagent, nothing added.

Stay tuned. 

Khalid Jebbari

unread,
Mar 27, 2015, 3:20:20 PM3/27/15
to clojur...@googlegroups.com
Staying tuned !

I'm not in SF, will there be slides or videos ?

Daniel Kersten

unread,
Mar 27, 2015, 3:56:26 PM3/27/15
to clojur...@googlegroups.com
I'm not in SF either (or even the same continent) so would very much appreciate slides/videos/blog posts.

Marc Fawzi

unread,
Mar 27, 2015, 4:00:52 PM3/27/15
to clojur...@googlegroups.com
Yes, for sure.

Daniel Kersten

unread,
Mar 27, 2015, 4:50:51 PM3/27/15
to clojur...@googlegroups.com
Excellent, thanks!
Reply all
Reply to author
Forward
0 new messages