As I said in the comment, this is rarely an issue with re-frame in my experience.
@p-himik Hooks come with certain rules, like they must be called in exactly the same order each time. And there can't be any conditional logic which might mean they are "in" for one render but not the next. Also, IMO resist the temptation to just embrace everything React/javascript, or else you'll find yourself swimming in the messy, washing machine churn that is javascript. Uggh. I think it is well worth trying to find the ClojureScript expression of ideas.
2014 React was a masterpiece. (And Reagent was a brilliant insight in addition to that. Thank you Dan!). What's happened to React since then ... not so impressive. In fact, it looks like a return to the previous messy churn of javascript, stumbling forward, one unsatisfactory solution after the next, always heralded with breathlessness, and then discarded two years later. IMO.
Hooks have been complected deep into the rendering machinery for only one reason: so enable the "suspense" feature. Which has greatly increased the complexity and hasn't even been delivered yet because of "reasons". And even when it is delivered, it can only be shown off in artificial benchmarks involving "Sierpinski Triangles". Hmm. I am exaggerating here to make a point. Way better, in my opinion, if they made React better at using shared workers (logic in one thread, render in another. cleanly separated, and not horribly intertwined). The "Sierpinski Triangles" demo of this arrangement would look just as good, if not better. Anyway, end of rant. (I reserve the right to completely change my mind later :-))
Idle thought of the day. I think re-frame handlers would be more composable if they returned two keys :db and :fx where :fx is a collection.
:db needs to be threaded through as each handler modifies the app-db in turn
:fx would allow all side effects to be kept in a list and executed in turn. No chance of clobbering each other.
e.g {:fx [{:dispatch ...}{:dispatch ...}{:dispatch ...}{:dispatch ...}]
So to compose you thread :db through handlers and collect up all :fxs returned.
Expecting answers such as: been there done that, you're a monster, shouldn't you use more interceptors...
@olivergeorge It is a good idle thought. I have made this comment elsewhere: if I had my time over again, I would absolutely make effects be a seq, rather than a map
Upside:
1. It gives ordering (not that useful, but an interesting property)
2. no need for dispatch-n
as you point out (this same -n
issues comes up a lot with other effects like http GETs etc
Downside:
1. slightly nosier visually with the additional structural nesting.
2. breaking change
So I'm interested in your idea because it is a way to take away Downside 2, maybe.
In terms of ordering, if I return both effects {:db ... :fx [....]}
which effect is actioned first? :db
?
What if we put dB inside fx?
I think :db is a special case. it's changing state... not triggering side effects. In fact, it's important to keep it out if you want to compose a bunch of simple handlers together which I think would be really nice and avoid a lot of typical boilerplate in handlers.
There can be only one
I guess there are cases where multiple fx would conflict (excepting :db) but none come to mind.
I suspect it doesn't matter if :fx or :db is actioned first since they are independent... fx never have access to current app-db state.
So long as all fx are triggered and the :db is updated before any other events are processed then it's fine. (In reflection, I'd do the :db first to allow for :fx which act on the app-db atom directly. Not something to encourage but logically what you'd expect - state has changed and after effects play out)
I suspect the {:db ... :fx []}
can be handled by re-frame v1 via an interceptor. Sounds fiddly but can't see why it wouldn't work.
Could even be a new type of event handler registration which handles the return value differently: re-frame.core/reg-event-fsm
(or whatever)
(so non-breaking addition to reframe)
probable noob question, but why is it that when i dereference a subscription inline like so
(let [my-data @(re-frame/subscribe [:my-sub])]
(fn []
[:div my-data]))
the data doesn’t refresh but it does when dereferenced like so
(let [my-data (re-frame/subscribe [:my-sub])]
(fn []
[:div @my-data]))
even though the todo example uses the inline approach? (https://github.com/day8/re-frame/blob/master/examples/todomvc/src/todomvc/views.cljs#L55)If fn
is your component, then by doing it in the outer let
you’re not deref’ng during the render phase. Reagent afaik will only track derefs that happen during render.
Right, and that can be a handy tool. For example, you want to populate a dropdown menu with values you get from an ajax call, but you don’t want to re-run the ajax call every time the component renders. So deref in the let bindings, and it will only happen once, when the component is mounted.
but i just tried it with the deref in the let bindings and it picked up a value now by removing my inner component
so now i have
(let [my-data @(re-frame/subscribe [:my-sub])]
[:div my-data])
and it picks up new data after initial renderseems like these two accomplish the same thing, but i’m not sure i understand the consequence to each
(let [my-data @(re-frame/subscribe [:my-sub])]
[:div my-data])
(let [my-data (re-frame/subscribe [:my-sub])]
(fn []
[:div @my-data]))
Does it re-render with new data if the data behind :my-sub
changes? That would surprise me.
what’s the whole component look like? you might be unintentionally creating a type 2 component.
If there’s a let binding outside the render function, it’s definitely a form-2 component.
^ IE the fn
inside the let
is what is different about your two examples
it’s gigantic, but a watered down version would be like this:
(defn home []
(let [alert (re-frame/subscribe [:alert])]
(fn []
[:div {:style home}
[:div {:style main} /* home content*/]
[:div {:style modal} @alert])))
I’m not at all sure about this, but it’s possible that if you deref the subscription inside the let binding, it will re-run your render function when the data behind [:my-sub]
changes. The problem is that it will just re-render my-data
, and that’s going to remain at whatever value came out of [:my-sub]
the first time you de-referenced it.
i would prefer not to re-render the entire home
component and just display a modal. my guess based on what you guys are saying is i want to deref in my let?
I would make the entire [:div {:style modal} @alert]
bit its own component--that’s the best way to avoid unnecessary re-rendering in the parent component, plus it makes it simpler to keep track of the subscriptions you need. Well, usually it does anyway.
very true, that makes sense, thanks guys
Thanks @mikethompson I'll look forward to seeing what others think about it. I've done my best to clarify my thoughts in a comment.