Closed clyfe closed 3 years ago
Is this different than binding?
Yes and no. It piggybacks on binding (unless we decide on some other mechanism).
But it's bound during descendant components (optimization: that actually make use of it) render calls.
Similarly with how *ratom-context*
or *current-component*
are bound during render calls.
But it's scheduled from a parent component via with-binding
.
I have this vague idea of how the interface could look like, but not the implementation. It's alike contexts in the sense that it teleports data to ancestors; with the benefit that it does not need explicit "getting" in the ancestor.
A more concrete example;
(ns my.view
(:require [tape.frame :as frame]))
(def my-frame (frame/frame)) ;; a Re-Frame instance of sorts
(defn child []
[:p "Data from my frame subscription: "
(frame/subscribe :sub-in-my-frame)]) ;; subscription in current frame
(defn parent []
(r/with-binding [frame/*current* my-frame]
[:<>
[:p "Parent"]
[child]]))
Where my-frame
is a Re-Frame instance of sorts.
You can probably build this yourself using React Context.
Using useContext
hook you can "automatically" retrieve the value in your subscribe
or such function:
https://reactjs.org/docs/hooks-reference.html#usecontext (but you need to enable functional components implementation: https://github.com/reagent-project/reagent/blob/master/doc/ReagentCompiler.md#functional-components-implementation)
I don't have time to write complete answer now, but here is quick list of notes why this is hard to implement and why Context API is probably better than dynamic bindings:
*ratom-context*
, *current-component*
or with-let
are not useful comparisons here, none of those needs to keep track of the component hierarchy.
If you have component c
inside components a
and b
which set different binding, where do you store the binding for c
?
We pretty much need Context or something very similar to that, to keep track of the bindings values for every component instance.
Adding any new properties or state to component instances adds more overhead to Reagent.
Setting bindings for each render call would be quite expensive. And we'd need to set bindings for EVERY call, as we don't know which components use it really. Context API is good because the performance cost is only paid when the component needs to retrieve the value (useContext
hook, or others).
And if you need this soon, I recommend looking into Context. If Reagent will implement something to help this, it will take time.
Thank you, useContext & functional components work. Closing this.
It seems hooks have limitations. Makes them not a full solution for hauling around a frame for subscribe/dispatch in the manner described. An explicit API would work (get the frame first-thing, then pass it to subscribe/dispatch); but not a behind-the-scenes one (just call subscribe/dispatch - it's them that acquire the frame).
Some of the same limitations would apply to dynamic bindings, e.g. event handlers:
(defn parent []
(r/with-binding [*context* "my-context"]
[:button {:on-click (fn [e]
Dynamic binding is not set when the event handler runs.
)}]))
For subscribe I guess it is quite good practice to create those in let
form on top level of the component.
If you need subscriptions for some list items, like inside for
, you are probably better to refactor for
to render list of components, and do the subscription inside that component.
Dispatch is probably harder, it makes sense to run those inside event handlers etc.
Thanks for the input! Will leave this closed, and just make my api a bit more verbose/explicit, since it's the most feasible currently.
Feature request:
with-binding
. A construct somewhat similar withwith-let
. It sets a binding for a dynamic var that's then on bound during the render of the component and all it's descendants. Similar to prior art of*ratom-context*
&*current-component*
. Required for: EP 002 - Multiple re-frame Instances: "Problem 2, Solution sketch 2: Hack Reagent" part at the end there.Implementation tips welcome.