re-frame

https://github.com/Day8/re-frame/blob/master/docs/README.md https://github.com/Day8/re-frame/blob/master/docs/External-Resources.md
2020-08-19T03:24:08.416200Z

Ideas and opinions welcome: https://github.com/day8/re-frame/issues/639#issuecomment-675788881

Oliver George 2020-08-19T05:08:09.417200Z

@mikethompson really like where this is going

Oliver George 2020-08-19T05:08:30.417700Z

Some thoughts here rather than a comment as I'm not sure I can distill it yet...

Oliver George 2020-08-19T05:09:16.418800Z

Specifically regarding reg-event-fx2.

Oliver George 2020-08-19T05:10:51.420500Z

The example in your comment is compatible with the current calling conventions. To compose event handlers from smaller functions the example handler prepares the initial value {:db... :fx...} and specially passes event as an arg to composable helpers (without this the helpers miss out on cofx)

Oliver George 2020-08-19T05:10:55.420700Z

Here's the what if...

Oliver George 2020-08-19T05:11:28.421400Z

What if the calling convention was different to streamline those tasks.

Oliver George 2020-08-19T05:12:14.422400Z

Call the handler with one arg which includes :db, :fx, :event and any other :cofx keys.

Oliver George 2020-08-19T05:12:44.422900Z

That can flow through all logic safely.

Oliver George 2020-08-19T05:13:49.424500Z

That different calling convention would justify a new reg- I think

Oliver George 2020-08-19T05:14:20.425200Z

For what it's worth I settled on the idea that "state" flows through the handler logic.

Oliver George 2020-08-19T05:14:40.425700Z

:db and :fx are "state" in the sense that they represent the (1) current state of the system as input, and (2) new "state" of the system as return value.

Oliver George 2020-08-19T05:15:02.426300Z

For :db the transition of state is (reset! app-db)

Oliver George 2020-08-19T05:15:25.426800Z

For :fx the transition of state is (doall (map do-effect ms))

Oliver George 2020-08-19T05:19:56.429800Z

It feels like we're moving closer to the core concepts of a finite state machine which might be a useful reference point for describing how it holds together.

p-himik 2020-08-19T05:20:48.430400Z

I think it sounds potentially useful. But isn't it orthogonal to :fx? Sure, :fx makes composing things easier, but composing is possible without it.

Oliver George 2020-08-19T05:23:36.431600Z

One thing which makes composing hard today is asymetry in the data shape in vs out

p-himik 2020-08-19T05:23:59.432200Z

Not really - there's reg-event-ctx.

Oliver George 2020-08-19T05:24:14.432500Z

Huh!

Oliver George 2020-08-19T05:25:31.433300Z

Shooting from the hip... definitely close to what I'm talking about in terms of calling convention. Perhaps specifically different in terms of how the return value (state change) is processed.

p-himik 2020-08-19T05:26:27.433900Z

The only hiccup here is the presence of the second argument, yes.

Oliver George 2020-08-19T05:29:45.435900Z

Pretty sure the event-v arg is always redundant since it can be accessed via ctx as the :event arg. (I should check but presumably injected via a standard cofx.)

p-himik 2020-08-19T05:32:26.438700Z

It is indeed redundant in terms of the available information. But it makes things a bit easier given that you almost always need to drill into that vector. With the second argument:

(fn [ctx [_ arg1 arg2 arg3]] ...)
Without the second argument:
(fn [{[_ arg1 arg2 arg3] :event :as ctx}] ...)

p-himik 2020-08-19T05:33:21.439500Z

I'm not saying that it precludes anyone from implementing a new reg-event-* function, of course.

Oliver George 2020-08-19T05:33:37.439800Z

It is very convenient for that I agree. Things like this might be common if the approach got up... (defn get-resp [s] (assoc-in s [:db :data] (get-in s [:event 1])))

p-himik 2020-08-19T05:34:08.440100Z

Yuk! :D

Oliver George 2020-08-19T05:34:19.440500Z

šŸ™‚

Oliver George 2020-08-19T05:35:04.441200Z

Today's lack of logic composability is also yuk... just more familiar šŸ™‚

Oliver George 2020-08-19T05:35:32.441900Z

I think there's interesting possibilities for using cofx to declare/type/spec/unpack/conform event args in useful ways

Oliver George 2020-08-19T05:35:43.442400Z

But it's not core to the idea

p-himik 2020-08-19T05:38:34.445Z

Just so there's something concrete to talk about - have you actually encountered some problems in the wild that would definitely be easier to solve were there event handlers composition available? Can you share them?

Oliver George 2020-08-19T05:39:50.446100Z

Shooting from the hip again....

Oliver George 2020-08-19T05:40:09.446600Z

One issue is that I have many events in my system which should be handled in similar ways.

Oliver George 2020-08-19T05:40:45.447600Z

Another is generalising something I do often.

Oliver George 2020-08-19T05:41:05.448100Z

Without easy composability those helpers become heavy to hookup/use

Oliver George 2020-08-19T05:41:27.448700Z

And without helpers I either duplicate logic

Oliver George 2020-08-19T05:41:33.449Z

Or dispatch more events

Oliver George 2020-08-19T05:41:46.449500Z

when the eventloop becomes an api you can make quite a mess

Oliver George 2020-08-19T05:42:06.450200Z

Not sure these are smoking guns but it's something I reach for often

Oliver George 2020-08-19T05:43:07.451600Z

I have to head out soon but will check back later. Thanks for your thoughts @p-himik

p-himik 2020-08-19T05:47:50.457Z

With easier composability you will have to be careful about the event vector that you pass to dispatch and handle in your event handlers. Creating complex behaviors by utilizing many simple functions that operate on just db/`ctx` and a set of named arguments makes it much more flexible than using comp where each function absolutely must expect the same kind of argument and absolutely must have the event vector have specified things at a very specific position. To be more concrete. Suppose I have this function that would work in the scenarios that you describe:

(defn add-a-to-db [ctx]
  (assoc-in ctx [:db :a] (get-in ctx [:event 1])))
As you can see, it expects events like [event-id value-of-a ...]. And then I also want to have this function:
(defn add-b-to-db [ctx]
  (assoc-in ctx [:db :b] (get-in ctx [:event 1])))
Do you see the problem here? These functions look composable, but they are not! If you decide to replace 1 in add-b-to-db with 2, then suddenly even the simplest event that just wants to add b, will have to use events like [event-id nil value-of-b] for no clear reason.

Oliver George 2020-08-19T05:50:21.457900Z

Yep, we're talking here about coupling your helper implementation with event structure

Oliver George 2020-08-19T05:51:25.459300Z

(If I was rebooting re-frame I'd use a map for the event data.)

Oliver George 2020-08-19T05:51:59.460Z

Does that apply to all types of data sharing... I'm trying to generalise your point so it's not about event data shape specifically.

Oliver George 2020-08-19T05:53:02.460700Z

Its not inherantly wrong to pass pass a second argument to your helper. Threading macro is our friend here.

Oliver George 2020-08-19T05:54:26.461600Z

I'll think more about this.

p-himik 2020-08-19T05:58:19.462600Z

> Threading macro is our friend here. And it's already perfectly possible with reg-event-fx. :)

(fn [ctx [_ a b]]
  (-> ctx
      (add-a-to-db ctx a)
      (add-b-to-db ctx b)))

Oliver George 2020-08-19T06:02:46.464400Z

A second argument (event-v) doesn't get in the way of the core idea.

p-himik 2020-08-19T06:13:26.465700Z

My point is that if you're content with not being able to use comp (which is the case with the second argument), then the core idea is already implemented by reg-event-ctx. I don't see how it can be improved to facilitate composition.

Oliver George 2020-08-19T06:22:36.466500Z

I'll have to try reg-event-ctx to comment. Great if it's useful.

Oliver George 2020-08-19T06:22:57.467100Z

My guess is (reg-event-ctx :x identity) would throw a heap of errors

Oliver George 2020-08-19T06:23:09.467500Z

Specifically because things in ctx don't marry up with :fx handlers

Oliver George 2020-08-19T06:23:28.467800Z

Happy to be wrong

p-himik 2020-08-19T06:36:13.468900Z

reg-event-ctx expects a function with 2 arguments. identity is a function with 1 argument. If you mean (reg-event-ctx :x (fn [ctx _] ctx)) then it will work just fine without any errors.

Oliver George 2020-08-19T08:08:09.471400Z

I stand corrected. Sounds fine then.

2020-08-19T06:42:09.469200Z

@olivergeorge > (If I was rebootingĀ re-frameĀ I'd use a map for the event data.) I can remember agonizing over that long and hard. But if I was doing it all again, I probably wouldn't change that decision. I'm relatively happy with that one.

2020-08-19T06:45:12.470200Z

(Which is not to say that maps wouldn't have some advantages ... just saying that I'm still happy with the overall balance)

p-himik 2020-08-19T06:45:43.470800Z

And right now you can still use events like [:event-id {:x 1 :y 2}].

Oliver George 2020-08-19T08:09:11.472100Z

Perhaps I'm drinking too much "positional args are the devil" cool aid. I do like a qualified key.

Oliver George 2020-08-19T08:10:13.473400Z

But that's all an aside to the ticket I think except that if @p-himik is right then perhaps a new reg isn't necessary. (I do need to see it to believe it fully though, can't see how it'd be tolerant of cofx keys appearing in return value when they don't match up to registered fx handlers). EDIT: Yeah technically reg-event-ctx would work and is more powerful than I see need for. Ticks the "simple" box but somewhat less "easy" for common use cases. I'd still vote for the handler arg format to be state-in (a map of cofx including :db and perhaps an empty :fx) and state-out (a map of :db, :fx and other stuff which is ignored).

Oliver George 2020-08-19T09:56:39.474400Z

I make it a point of contradicting myself at the earliest convenience.

šŸ‘ 1
Oliver George 2020-08-19T09:56:47.474700Z

Re: maps vs positional args...

Oliver George 2020-08-19T09:57:14.475300Z

I think it'd be nice to open the door to multiple args for fx handlers. Accepting vectors or maps in the :fx seq would do that.

Oliver George 2020-08-19T09:58:22.476200Z

implementation could be something like...

(defn do-fx [ms]
  (doseq [m ms [id & args] m]
    (apply (get-fx id) args)))

Jason 2020-08-19T17:50:11.479500Z

is it fair to say that if a js component uses react hooks it cannot be used in a re-frame app? Edited to add "yes"

lilactown 2020-08-19T18:53:16.480Z

it should work fine. are you running into issues?

lilactown 2020-08-19T18:53:34.480400Z

I should say, it should work fine as long as youā€™re using a recent version of React that includes hooks

Jason 2020-08-19T19:28:32.480600Z

Thanks for replying. I would love to be proven wrong. Re: React version, I'm on react and react-dom 16.13.1. I've been trying to replicate https://material-ui.com/components/popover/#mouse-over-interaction in my re-frame app. The sample code (click "<>") uses react hooks.

Jason 2020-08-19T19:30:58.480900Z

My best guess cljs is

(defn hover-sample [{:keys [^js classes]}]
  (let [[anchorEl setAnchorEl] (react/useState nil)]
    [:&gt; Paper {:class [(.-paper classes) (.-fixedHeight classes)]}
     [:div
      [:&gt; Typography {:id "hover-anchor"
                      :aria-haspopup "true"
                      :onMouseEnter #(setAnchorEl (-&gt; %
                                                      .-target))
                      :onMouseLeave #(setAnchorEl nil)}
       "Hover with a popover"]
      [:&gt; Popover {:id "mouse-over-popover"
                   :className (.-popover classes)
                   :classes {:paper (.-paper classes)}
                   :open (not= nil anchorEl)
                   :anchorEl anchorEl
                   :anchorOrigin {:vertical "bottom"
                                  :horizontal "left"}
                   :transformOrigin {:vertical "top"
                                     :horizontal "left"}
                   :onClose #(setAnchorEl nil)
                   :disableRestoreFocus true}
       [:&gt; Typography "I use Popover."]]]]))
but that violates hook rules. If there's a way to refactor this to play nice, I am all ears

lilactown 2020-08-19T19:31:06.481100Z

ah, so you need to use a react hook inside of a reagent component

lilactown 2020-08-19T19:31:41.481300Z

thatā€™s a different case than use a component, which uses hooks šŸ˜„ but we can still get there

Jason 2020-08-19T19:33:31.481500Z

Please pardon my newb. I did say "in a re-frame app" so I thought I was implying the rest. I guess not!

lilactown 2020-08-19T19:34:41.481700Z

well, the distinction Iā€™m making is the difference between:

[:&gt; SomeReactComponentThatUsesHooks {:foo "bar"}]
and actually using a hook inside of a component you are authoring using reagent. does that make sense?

lilactown 2020-08-19T19:34:57.481900Z

(Iā€™m working on an example solution for you here as well)

Jason 2020-08-19T19:37:11.482100Z

I am daring to believe I'm not out of luck. It feels great

lilactown 2020-08-19T19:37:32.482300Z

in this case, we donā€™t actually need to use useState. the material docs use a useState hook because itā€™s a convenient way to create some local state; the most convenient way to handle this in reagent is to use a local atom, like:

(defn hover-sample [_]
  (let [anchor-el (reagent.core/atom el)]
    (fn [{:keys [^js classes]}]
      [:&gt; Paper {:class [(.-paper classes) (.-fixedHeight classes)]}
       [:div
        [:&gt; Typography {:id "hover-anchor"
                        :aria-haspopup "true"
                        :onMouseEnter #(reset! anchor-el (.-target % ))
                        :onMouseLeave #(reset! anchor-el nil)}
         "Hover with a popover"]
        [:&gt; Popover {:id "mouse-over-popover"
                     :className (.-popover classes)
                     :classes {:paper (.-paper classes)}
                     :open (not= nil @anchor-el)
                     :anchorEl @anchor-el
                     :anchorOrigin {:vertical "bottom"
                                    :horizontal "left"}
                     :transformOrigin {:vertical "top"
                                       :horizontal "left"}
                     :onClose #(reset! anchor-el nil)
                     :disableRestoreFocus true}
         [:&gt; Typography "I use Popover."]]]])))

Jason 2020-08-19T19:39:08.482900Z

I had a local ratom version which had firing hover events before I tried to do hooks, but let me try yours

lilactown 2020-08-19T19:40:23.483200Z

thereā€™s currently an alpha version of reagent which does allow you to use hooks inside of reagent components but I donā€™t really understand how to use it tbh

lilactown 2020-08-19T19:40:46.483400Z

the docs have not been updated AFAICT

Jason 2020-08-19T19:41:26.483600Z

Compiler says 'el' is not defined in the ratom init

lilactown 2020-08-19T19:41:48.483800Z

oops, should be nil

Jason 2020-08-19T19:45:22.484Z

that builds and renders, but this is where i ended up with my local ratom attempt too. When I put the mouse over the anchor element and leave it stationary, I can see the mouse flicker

Jason 2020-08-19T19:46:49.484200Z

when i had my local ratom set up to log events to the console, it was logging :onEnter and :onLeave in pairs, continuously

Jason 2020-08-19T19:49:57.484400Z

FWIW, i can also say that if i comment out the :onLeavehandler, the popup shows up (and stays there)

lilactown 2020-08-19T19:58:10.484600Z

I wouldnā€™t expect that to change whether you use local react state, or a local ratom

lilactown 2020-08-19T19:58:16.484800Z

but I could be wrong

Jason 2020-08-19T19:59:59.485Z

I sure didn't expect it but as a newb to front-end I thought there might be some magic in using hooks so I was trying that in desperation

lilactown 2020-08-19T20:00:46.485200Z

thereā€™s a way we can test this

lilactown 2020-08-19T20:01:08.485400Z

all JSX is a syntax for calling React.createElement

Jason 2020-08-19T20:02:09.485600Z

yes, i've used the reagent wrapper elsewhere in this code

lilactown 2020-08-19T20:05:22.485800Z

(ns my-app.some-feature
  (:require
    ["react" :as react]
    [goog.object :as gobj]
    ...))


(def $ react/createElement)


(defn hover-sample* [props]
  (let [^js classes (gobj/get props "classes")
        [anchorEl setAnchorEl] (react/useState nil)]
    ($ Paper #js {:class #js [(.-paper classes) (.-fixedHeight classes)]}
     ($ "div"
      ($ Typography #js {:id "hover-anchor"
                         :aria-haspopup "true"
                         :onMouseEnter #(setAnchorEl (-&gt; %
                                                      .-target))
                         :onMouseLeave #(setAnchorEl nil)}
         "Hover with a popover")
      ($ Popover #js {:id "mouse-over-popover"
                      :className (.-popover classes)
                      :classes {:paper (.-paper classes)}
                      :open (not= nil anchorEl)
                      :anchorEl anchorEl
                      :anchorOrigin #js {:vertical "bottom"
                                         :horizontal "left"}
                      :transformOrigin #js {:vertical "top"
                                            :horizontal "left"}
                      :onClose #(setAnchorEl nil)
                      :disableRestoreFocus true}
       ($ Typography "I use Popover."))))))


(defn hover-sample [{:keys [classes]}]
  ($ hover-sample* #js {:classes classes}))

Jason 2020-08-19T20:07:34.486100Z

...just a sec while i figure out how to integrate that....

lilactown 2020-08-19T20:07:55.486300Z

ā˜ļø:skin-tone-2: the above creates a normal functional react component hover-sample* that uses hooks. hover-sample is a reagent component that handles interop between reagent & react. you could also skip that and just call [:&gt; hover-sample* {:classes ,,,}] I just wasnā€™t sure if you wanted to rewrite all the callers

Jason 2020-08-19T20:09:42.486500Z

me either just yet šŸ™‚ , thinking

lilactown 2020-08-19T20:11:33.486700Z

hover-sample* basically completely routes around trying to use reagent, which is what allows us to use hooks. itā€™s then easy to use the hover-sample* component in our reagent project

lilactown 2020-08-19T20:12:48.486900Z

this is what I meant by distinguishing between: > [using] a js component [that] uses react hooks and > using hooks inside of a reagent component

Jason 2020-08-19T20:13:51.487100Z

i see. hover-smaple is of course buried in a view so this will take some fiddling

lilactown 2020-08-19T20:14:02.487300Z

šŸ˜¬

Jason 2020-08-19T20:29:24.487500Z

In order to cut out all callers, i'm transplanting this into my main.cljs, in mount-root. If that's a bad idea, feel free to say so

lilactown 2020-08-19T20:31:51.487700Z

i don't really know what you mean but whatever we can do to test to see if the flickering goes away

Oliver George 2020-08-19T21:12:51.490Z

There's a functional component branch in the reagent repo with aims to improve this sort of thing (I think).

Jason 2020-08-19T21:21:15.490100Z

i'm still here, trying to get the classes into the props. Please bear with me

Jason 2020-08-19T21:43:50.000700Z

Thanks, that's been added to my list of things to try

lilactown 2020-08-19T21:45:56.000800Z

nw, I went and did some yoga šŸ˜„

Jason 2020-08-19T22:12:02.001Z

As mentioned, I'm new at this stuff and I am now getting errors which make me wonder whether I'm trying to do the right thing. My code is based on https://github.com/dakra/mui-templates and I'm trying to insinuate hover-sample https://github.com/dakra/mui-templates/blob/9f3a997856af199394c7dbc31a4432397590a268/src/mui_templates/main.cljs#L142 , my version of which is unchanged. Would you care to offer an opinion? I've transplanted styles and a with-styles wrapper from dashboard.cljs but am getting

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
I hate to ask but I'm not sure which way to head

Jason 2020-08-19T22:33:54.001400Z

Wait, I may be misunderstanding how far up the call stack i have to go. Can I use your hover-sample inside other material-ui components?

lilactown 2020-08-19T23:12:51.001700Z

I donā€™t really see where youā€™re trying to use the example code I pasted

lilactown 2020-08-19T23:14:09.001900Z

I would try and create a minimal example, instead of rewriting a huge component like the dashboard component in your project

Jason 2020-08-19T23:39:43.002100Z

I'll keep trying to get more minimal. Thanks for your patience