Ideas and opinions welcome: https://github.com/day8/re-frame/issues/639#issuecomment-675788881
@mikethompson really like where this is going
Some thoughts here rather than a comment as I'm not sure I can distill it yet...
Specifically regarding reg-event-fx2.
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)
Here's the what if...
What if the calling convention was different to streamline those tasks.
Call the handler with one arg which includes :db, :fx, :event and any other :cofx keys.
That can flow through all logic safely.
That different calling convention would justify a new reg-
I think
For what it's worth I settled on the idea that "state" flows through the handler logic.
: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.
For :db the transition of state is (reset! app-db)
For :fx the transition of state is (doall (map do-effect ms))
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.
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.
One thing which makes composing hard today is asymetry in the data shape in vs out
Not really - there's reg-event-ctx
.
Huh!
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.
The only hiccup here is the presence of the second argument, yes.
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.)
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}] ...)
I'm not saying that it precludes anyone from implementing a new reg-event-*
function, of course.
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])))
Yuk! :D
š
Today's lack of logic composability is also yuk... just more familiar š
I think there's interesting possibilities for using cofx to declare/type/spec/unpack/conform event args in useful ways
But it's not core to the idea
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?
Shooting from the hip again....
One issue is that I have many events in my system which should be handled in similar ways.
Another is generalising something I do often.
Without easy composability those helpers become heavy to hookup/use
And without helpers I either duplicate logic
Or dispatch more events
when the eventloop becomes an api you can make quite a mess
Not sure these are smoking guns but it's something I reach for often
I have to head out soon but will check back later. Thanks for your thoughts @p-himik
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.Yep, we're talking here about coupling your helper implementation with event structure
(If I was rebooting re-frame I'd use a map for the event data.)
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.
Its not inherantly wrong to pass pass a second argument to your helper. Threading macro is our friend here.
I'll think more about this.
> 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)))
A second argument (event-v) doesn't get in the way of the core idea.
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.
I'll have to try reg-event-ctx to comment. Great if it's useful.
My guess is (reg-event-ctx :x identity) would throw a heap of errors
Specifically because things in ctx don't marry up with :fx handlers
Happy to be wrong
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.
I stand corrected. Sounds fine then.
@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.
(Which is not to say that maps wouldn't have some advantages ... just saying that I'm still happy with the overall balance)
And right now you can still use events like [:event-id {:x 1 :y 2}]
.
Perhaps I'm drinking too much "positional args are the devil" cool aid. I do like a qualified key.
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).
I make it a point of contradicting myself at the earliest convenience.
Re: maps vs positional args...
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.
implementation could be something like...
(defn do-fx [ms]
(doseq [m ms [id & args] m]
(apply (get-fx id) args)))
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"
it should work fine. are you running into issues?
I should say, it should work fine as long as youāre using a recent version of React that includes hooks
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.
My best guess cljs is
(defn hover-sample [{:keys [^js classes]}]
(let [[anchorEl setAnchorEl] (react/useState nil)]
[:> Paper {:class [(.-paper classes) (.-fixedHeight classes)]}
[:div
[:> Typography {:id "hover-anchor"
:aria-haspopup "true"
:onMouseEnter #(setAnchorEl (-> %
.-target))
:onMouseLeave #(setAnchorEl nil)}
"Hover with a popover"]
[:> 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}
[:> Typography "I use Popover."]]]]))
but that violates hook rules. If there's a way to refactor this to play nice, I am all earsah, so you need to use a react hook inside of a reagent component
thatās a different case than use a component, which uses hooks š but we can still get there
Please pardon my newb. I did say "in a re-frame app" so I thought I was implying the rest. I guess not!
well, the distinction Iām making is the difference between:
[:> SomeReactComponentThatUsesHooks {:foo "bar"}]
and actually using a hook inside of a component you are authoring using reagent. does that make sense?(Iām working on an example solution for you here as well)
I am daring to believe I'm not out of luck. It feels great
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]}]
[:> Paper {:class [(.-paper classes) (.-fixedHeight classes)]}
[:div
[:> Typography {:id "hover-anchor"
:aria-haspopup "true"
:onMouseEnter #(reset! anchor-el (.-target % ))
:onMouseLeave #(reset! anchor-el nil)}
"Hover with a popover"]
[:> 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}
[:> Typography "I use Popover."]]]])))
this is using a form-2 component: https://github.com/reagent-project/reagent/blob/master/doc/CreatingReagentComponents.md#form-2--a-function-returning-a-function
I had a local ratom version which had firing hover events before I tried to do hooks, but let me try yours
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
the docs have not been updated AFAICT
Compiler says 'el' is not defined in the ratom init
oops, should be nil
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
when i had my local ratom set up to log events to the console, it was logging :onEnter
and :onLeave
in pairs, continuously
FWIW, i can also say that if i comment out the :onLeave
handler, the popup shows up (and stays there)
I wouldnāt expect that to change whether you use local react state, or a local ratom
but I could be wrong
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
thereās a way we can test this
all JSX is a syntax for calling React.createElement
yes, i've used the reagent wrapper elsewhere in this code
(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 (-> %
.-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}))
...just a sec while i figure out how to integrate that....
āļø: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 [:> hover-sample* {:classes ,,,}]
I just wasnāt sure if you wanted to rewrite all the callers
me either just yet š , thinking
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
this is what I meant by distinguishing between: > [using] a js component [that] uses react hooks and > using hooks inside of a reagent component
i see. hover-smaple
is of course buried in a view so this will take some fiddling
š¬
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
i don't really know what you mean but whatever we can do to test to see if the flickering goes away
https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#hooksy understanding is yes but it's sort of a special case: https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#hooks
There's a functional component branch in the reagent repo with aims to improve this sort of thing (I think).
i'm still here, trying to get the classes into the props. Please bear with me
Thanks, that's been added to my list of things to try
nw, I went and did some yoga š
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 headWait, 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?
I donāt really see where youāre trying to use the example code I pasted
I would try and create a minimal example, instead of rewriting a huge component like the dashboard
component in your project
I'll keep trying to get more minimal. Thanks for your patience