Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Make Views Great Again #299

Open
mike-thompson-day8 opened this issue Dec 19, 2016 · 5 comments
Open

Make Views Great Again #299

mike-thompson-day8 opened this issue Dec 19, 2016 · 5 comments

Comments

@mike-thompson-day8
Copy link
Contributor

mike-thompson-day8 commented Dec 19, 2016

View functions with a subscription are not pure. They are using a data input from a non-argument source.

(defn greet 
   [] 
  [:div "Hello "  @(subscribe [:name])])    ;; in comes some data

From a practical point view, I haven't been motivated to solve this problem previously - given the way re-frame structures your app, non-local reasoning is not a problem in this case, and neither is testing because subscribe is trivial to stub out, so it feels like you are dealing with a pure function - however from a purist's point of view, this is certainly a "pebble in the shoe". In due course, I'd like it fixed, but I'm relaxed about when.

Here's a quick sketch of approximately how (not all issues resolved):

  1. create a new reg-view function thru which all views are associated with an id. Make this
    registration much like reg-sub in the way that you can nominate N signal inputs via a signals function.

  2. change Reagent so that the first element of vector can be an id previously registered by
    reg-view. So, instead of [:div ...], you could use [:something/panelX ...]

Would be used like this (notice how similar this is to reg-sub):

(re-frame.core/reg-view           ;;  <--- new 
   :a-view-id                              ;; an id for this view

   ;; input signals function 
   ;; what it returns will become the first param passed to the computation function
   (fn  [item-id  another]         ;; will be given props 
        (subscribe [:item item-id]))

   ;; computation function which returns hiccup
   (fn [item item-id another]     ;; given props BUT with values from subscription prepended
      [:div (:name item)]))

The computation fn becomes pure. The "subscribed to" values are put in the first argument
(same pattern as is currently used with reg-sub).

Later, when you wanted to use this view:
[:a-view-id 12 "hello"]
Reagent would look up the view id a-view-id and use the associated render function.

Significant Open Questions:

  • how to do Form-2 views or Form-3? This could be a stumbling block
  • Reagent treats the first prop as special. If it is a map it can contain keys etc.
  • Will Reagent accept this proposal?
@martinklepsch
Copy link
Contributor

martinklepsch commented Dec 19, 2016

Instead of a deep reaching change like this it might also make sense to consider advocating higher order components. E.g. users could define components that use subscribe but on their own don't render much themselves and instead pass down data to sub components.

This way most components that actually render DOM nodes would remain pure.

At the very least it should be considered what an alternative approach adds over using higher order "data supply" components.

This is just a quick thought, so I'm hope I didn't miss anything critical.

@danielytics
Copy link

danielytics commented Jan 18, 2017

@martinklepsch so essentially a similar idea to wrapping stateful components: https://github.com/Day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md

That is, there's an inner and outer component. The outer component handles the subscriptions and passes them, as data, to the pure inner component.
Seems like a reasonable convention to me and also means that the inner components are less re-frame specific (well, they still are if they call dispatch in event handlers, unless the react/dom event handlers also get passed down from the outer component).

@bowd
Copy link

bowd commented Feb 2, 2017

@danielytics Exactly! The combination of sending both state and actions as props from the wrapper is the same pattern that redux advocates and it makes a lot of sense.

It also makes it much easier to reason about the components when testing. You have the wrapper where the test is literally a spec or a description of expectations:

  • it should subscribe to this and that
  • calling this callback should dispatch that

And then the inner view is just presentation, plus maybe some local state ratoms like expanded/collapsed etc.

@dijonkitchen
Copy link
Contributor

I've done something similar using the "outer" container components to inject the subscriptions into the "inner" presentational pure function components.

Perhaps something like a higher-order function/component that merges in some subscription as a prop to a function/component, so you can string together a bunch of subscriptions in one thread-first macro?

@AndreaCrotti
Copy link
Contributor

We also have more "pure" components in our projects built on top of re-frame.
Looks like many people went through the same route, but it's maybe a library that should be separate from re-frame imho.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants