This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-07-20
Channels
- # aleph (3)
- # beginners (14)
- # cider (70)
- # cljdoc (30)
- # cljs-dev (1)
- # cljsjs (6)
- # clojars (7)
- # clojure (88)
- # clojure-greece (1)
- # clojure-italy (3)
- # clojure-nl (17)
- # clojure-spec (1)
- # clojure-uk (54)
- # clojurescript (48)
- # code-reviews (2)
- # cursive (28)
- # datascript (3)
- # datomic (20)
- # docs (1)
- # emacs (16)
- # figwheel-main (17)
- # fulcro (13)
- # graphql (2)
- # hyperfiddle (2)
- # jobs (2)
- # nyc (1)
- # off-topic (39)
- # parinfer (1)
- # re-frame (37)
- # reagent (225)
- # remote-jobs (3)
- # ring (3)
- # ring-swagger (1)
- # shadow-cljs (110)
- # spacemacs (10)
- # spirituality-ethics (1)
- # test-check (3)
- # tools-deps (36)
- # uncomplicate (2)
- # vim (7)
Is this correct: for a form 3 component, what may have been done in the now deprecated :component-will-update
should now be done in :component-did-update
?
Actually, I'm now thinking I don't want/need to do either of these. The situation is I'm painting new stuff on a canvas based on updates from a server that is pushing the data. Currently the idea was that the 'content' was rebuilt in the :component-will-update
function, but seems like this can be rebuilt in the message handler and things will just work. Yes/no?
If it can be done in component did mount do it there. That’s always the most stable place.
Doing it in the message handler might be annoying because it might split the logic up. Hard to say without seeing. I guess if it is incremental and it’s a canvas that doesn’t resize that might work.
@lee.justin.m Thanks for you input on this already. But I am scratching my head and I think you pretty savvy about react stuff, so let me ask another question - it may be just the question "am I totally lost?"
Anyway, I could not get the new canvas updates to draw unless I used component-will-update. Nothing else was working when using a form 3. But I kept whittling it down by trying various stuff. Also tried using Reacts so called 'totally uncontrolled component with keys' - never could get that to render anything. So, I did punt out to - when msg comes in from server, repaint canvas outside of component and then just rerender. This always seems to work - I was able to remove all but the render function from the component. Which I think means I could simply use a form 1 or maybe form 2 component.
But, the canvas paint is outside the react/reagent flow. Is this typically considered "bad"?? Thanks for your input!
Oh, one other thing that seems nicer this way is that I no longer get any 'flicker'. Previously when painting inside did mount/will update, the graphic always flickered - like it repainted twice. Now that never happens
in my canvas, the render func just returns a target div (you could directly return the canvas element, but i’m using a library that creates the canvas for me). i use a ref callback to get a handle to the node. then, in component-did-mount and component-did-update (both of which fire after render), i just call the canvas-drawing code (same function for both).
I’m not sure what’s causing the flickering--it is surprising to me that putting it in the render func fixed that becasue will-update/render/did-update all fire together in sequence
one other thing to note: depending on what you are passing in, you may well need to implement should-component-update to avoid unnecessary re-renders. my canvas is the only place in my code where i’ve needed that
I’m not quite sure what the real issue is here. Clearly you’re not getting your lifecycle methods hooked up. In terms of whether it is “bad” to be outside the react/reagent flow, it kind of depends. Canvas painting is always outside of the flow in some sense. It is cleaner, of course, to have all of the code related to the canvas in one place instead of having it split between some message handler code and some reagent code. but don’t let perfect be the enemy of good
Thanks. I think I'm still confused on several things with what is going on. In answer to your suspicion about this 'working in the render fn' I must have misled you there - it definitely does not work there. It was 'working' when the draw was outside altogether - in the msg handler. But I notice now that I was confused about what was happening. Once the div (holding the canvas) is rendered the drawing on canvas in the msg handler was the thing 'updating' the element - the reagent/react component was no longer even being called. So, that looks like a bad way to go.
I agree that I am clearly messing up the lifecycle stuff - well if I use the did-mount with will-update, then things do seem to work the way that flow was intended, but since will-update is going away and deprecated, I would prefer to move away from that.
I'm using a lib that creates the canvas as well. So, whatever you are doing probably is the right way to go. What is the ref callback you mention - or is that part of how the lib you are using works? @lee.justin.m Thanks again!
so in your render, you’ll need to get a ref to the div node so that you can pass it to the library
A bit more: when trying to use did-mount and did-update, both have this body: (visualize new-spec (rgt/dom-node comp))
It works the first time (probably the did-mount), but after nothing ever updates
Yeah, I have logging all over and that's how I found out I was lost. That :ref idea could be just the trick!
oh, so when your message handler receives data from the server, is it passing it down as props? you have to do something to actually trigger the react lifecycle remember
I'm sure that is where I'm getting confused - the msg handler (when not doing the canvas writing itself...) was updating the ratom which the visualize component was derefing.
right so that’s not going to work because you aren’t dereffing the ratom in the render function
the way i solve this problem is to wrap: a dumb outer component derefs the data and passes everything as props to the inner component
Hmmmm, let me give you what the main bits of this are. I'm not sure I can make a code fragment in a thread, but I'll try:
(defn vgl
"Reagent component to render vega/vega-lite visualizations."
[spec]
(rgt/create-class
{:display-name "VGL"
:component-did-mount
(fn [comp] (visualize spec (rgt/dom-node comp)))
:component-did-update
(fn [comp [_ new-spec]] (visualize new-spec (rgt/dom-node comp)))
:reagent-render (fn [spec] [:div#app #_{:key (next-key)}])}))
(defn hanami [ws]
(when-let [spec (get-adb [ws :cur-vgl])] [vgl spec]))
(when-let [elem (js/document.getElementById "app")]
(printchan "Element 'app' available")
(rgt/render [hanami :foo] elem))
visualize
is the thing that uses the lib that creates/updates canvas
get-adb derefs the ratom involved. So, upon the first update of that ratom everything works the way I would hope. Subsequent updates with new specs - nothing happens. But if I change did-update to will-update, everything just works
did-update takes [this old-argv]
. you need to access the current props by calling (reagent/props this)
@U06C63VL4 ^^ not sure if you saw this. i was busy chatting elsewehre for a while
Hi, been messing with it using that last info. Something has indeed changed to make it start 'working'. But I'm still confused about what the hell is going on 🙂 Here's latest
(defn vgl
"Reagent component to render vega/vega-lite visualizations."
[spec]
(printchan "VGL called")
(rgt/create-class
{:display-name "VGL"
:component-did-mount
(fn [comp] (visualize spec (rgt/dom-node comp)))
:component-did-update
(fn [comp [preprop prestate snapshot]]
(printchan (rgt/props comp) " snap: " snapshot)
(printchan "PreProps " preprop " PreState" prestate)
(visualize prestate (rgt/dom-node comp)))
:reagent-render (fn [spec] [:div#app #_{:key (next-key)}])}))
OK, so, the print of (rgt/props comp) and snapshot just prints snap:
- nothing before or after that I can see. The print of preprops prints a function with args (props, context, updater) and prestate is [object, object]
What is weird to me is that prestate here should be the same positional arg as I had before, but now it works. So, does calling (rgt/props comp) somehow magically change it?
let me just try that
I believe that did-mount’s signature is [this argv]
. the first argument of argv
is … this
OK, this still works:
:component-did-update
(fn [comp [preprop prestate snapshot]]
#_(printchan (rgt/props comp) " snap: " snapshot)
#_(printchan "PreProps " preprop " PreState" prestate)
(visualize prestate (rgt/dom-node comp)))
Previously (using the signature of will-update) I had [comp [ new-spec]] for the signature. That is definitely wrong, but the position of prestate in the new signature matches the old newspec. I wasn't getting any errors - as in wrong signature when using that 'bad signature' for did-update, just nothing happening.
So, it looks like preprop is some function (?!) prestate is actually the new state (!?!) and snapshot is some unknown [object object] (???). None of this seems to match what the react docs say
!!!???!?!
So, what is you idea of what (rgt/props this) is supposed to do/return?? (here I'm using comp for this). It just shows as nothing in the print - not undefined or null or nil - just nothing at all
Maybe I'm just not including any such thing (by random chance...)
I see that reagent claims the sig for did-update is [this old-argv] but the stuff passed is actually the new state
Nothing like getting all twisted up - I was just plain wrong about this working - chasing a ghost... The reason why I thought it worked was I had inadvertently left the canvas paint in the data update handler (outside of reagent/react). When I get rid of that, and keep the new correct sig for did-update, all the prints come out, but nothing is rendered... Ugh. Frustrating...
Actually, I changed the print of prestate so that I call (js->clj prestate) before printing - and ..... (drum rolll) it is indeed the old state. Which is why nothing happens it just redraws the same thing
That actually makes sense since did-update is called after renderer
at least according to react
You know, I don't understand why will-update has been junked - with the new flow they have I don't see anyway to make this work within their flows. The so called 'completely uncontrolled component' looked like a mechanism but you can change the keys all you want on the div and nothing updates.
oh sorry i led you astray: i forgot a tidbit about r/props
: “props” are only set if the first argument to the component is a map
this works in my thing because i pass a big map as the single argument to my component
OK .... I'm lost - would that be the 'spec' argument in my vgl component?
That 'spec' arg is a map, but it is the actual vgl-spec - not some map containing the vgl spec
well - react docs specifically says that it is the OLD argv
which, as you say, is crazy and make did-update basically worthless
What do you pass as that map? Something with a props key and state key or ??
Is the :val
some kind of magic dust??
see #reagent — componentDidMount is supposed to receive previous props per the react docs
there is also r/argv
which is probably what you want: see the updated gist https://gist.github.com/jmlsf/7fe736c8b6d7f236361aaad59a1113fd
I'll take a look at that, but for the moment this works (though I have no idea what the hell is going on):
(defn vgl
"Reagent component to render vega/vega-lite visualizations."
[spec]
(printchan "VGL called")
(rgt/create-class
{:display-name "VGL"
:component-did-mount
(fn [comp] (visualize (:val spec) (rgt/dom-node comp)))
:component-did-update
(fn [comp [preprop prestate snapshot]]
(printchan "Did-Update: called")
#_(printchan (rgt/props comp) #_" / " #_(rgt/children comp))
#_(printchan #_"PreProps " #_preprop " PreState" (js->clj prestate))
(visualize (:val (rgt/props comp)) (rgt/dom-node comp)))
:reagent-render (fn [spec] [:div#app #_{:key (next-key)}])}))
(defn hanami [ws]
(when-let [spec (get-adb [ws :cur-vgl])]
#_(visualize spec (js/document.getElementById "app"))
(printchan "Hanami called with " ws)
[vgl {:val spec}]))
(rgt/render [hanami :foo] elem)
okay so you can just get rid of the {:val ..}
wrapper and just call (first (rgt/children comp))
instead of (:val (rgt/props comp))
the basic rule is: each positional argument to your component gets pass as a child unless the first positional argument is a map, in which case that one argument is passed as props and all subsequent args are passed as children
Holy shit - so, r/argv can obtain the current argument vector from the this
arg of the dispatch and the actual dispatch is passed the prev-argv. That is among the most idiotic structuring ever. On top of that, it looks like r/props returns the second element of the current argv and r/children returns basically (rest argv).
That rule makes absolutely no sense - and worse yet, I don't see anything like that documented anywhere
OK, right
What possible "rationale" is behind this crazy idea
everything about this so that you can write simple functions and pass positional parameters
well I think. the guy who made this mess disappeared off the map in 2016, so it is mostly juho who does maintenance and a few people who hang out in #reagent and keep the wheels from coming off the bus
I don't see how that explains it - you could just do that with ONE consistent and documented scheme and be done with ti
but if you look at the change history, dan didn’t want to write (defn my-comp [props children])
he wanted to write (defn my-comp [arg1 arg2 arg3])
✔️ understood - I know you didn't have anything to do with it as I believe you are relatively new to it (from JS/React land)
OK, this works and is based on your @lee.justin.m last suggestion:
(defn vgl
"Reagent component to render vega/vega-lite visualizations."
[spec]
(printchan "VGL called")
(rgt/create-class
{:display-name "VGL"
:component-did-mount
(fn [comp] (visualize spec (rgt/dom-node comp)))
:component-did-update
(fn [comp old-useless-argv]
(printchan "Did-Update: called")
(let [new-useful-argv (rgt/children comp)
new-spec (first new-useful-argv)]
(printchan "NEWARGV " new-useful-argv)
(visualize new-spec (rgt/dom-node comp))))
:reagent-render (fn [spec] [:div#app #_{:key (next-key)}])}))
(defn hanami [ws]
(when-let [spec (get-adb [ws :cur-vgl])]
#_(visualize spec (js/document.getElementById "app"))
(printchan "Hanami called with " ws)
[vgl spec]))
I can't thank you enough - I doubt I would have figured out what was really going on - I'm guessing you looked at the code, but I probably would have given up and just used a hack
I thought you called with a map
I mean some 'nested map' - spec is a map, but it doesn't seem to pass muster as the magic map
It's actually just plain crazy/broken
i’m updating the docs and i think i’ll suggest the users just use (rest (r/argv this))
b/c that will always work
Well, despite spent hours wandering around in the 'funhouse', I managed to fall out of the house of mirrors exhibit and am now a happy camper with code that should be ok moving forward!
That certainly would have saved a lot of grief. But a couple of things:
First, in "Alternately, you can use (reagent/props this)
and (reagent/props children)
," shouldn't that latter be (reagent/children this)
? For me, that is what returns the new argv.
Second, I still do not understand the "the arguments to your render function are actually passed as children (not props) to the underlying React component, *unless the first argument is a map.*" part. The reason is, in my case I am passing a map as (the only) argument, and the new-argv is (first (r/children this)), where (r/children this) is a vector of only the original map passed; [arg1], where arg1 is the map passed. To get the behavior of "unless first argument is a map" I had to wrap my map in another map and pass that, eg, {:val arg1}
Here's a couple other pieces of data on this. Both of these printouts come from the same call. The first is (rgt/children this)
and the second is (rgt/argv this)
:
[#js {:height 200, :width 250, :data #js {:url "data/seattle-weather.csv"}, :layer #js [#js {:mark "bar", :encoding #js {:x #js {:timeUnit "month", :field "date", :type "ordinal"}, :y #js {:aggregate "mean", :field "precipitation", :type "quantitative"}}} #js {:mark "rule", :encoding #js {:y #js {:aggregate "mean", :field "precipitation", :type "quantitative"}, :color #js {:value "firebrick"}, :size #js {:value 3}}}]}]
[#object[Function] #js {:height 200, :width 250, :data #js {:url "data/seattle-weather.csv"}, :layer #js [#js {:mark "bar", :encoding #js {:x #js {:timeUnit "month", :field "date", :type "ordinal"}, :y #js {:aggregate "mean", :field "precipitation", :type "quantitative"}}} #js {:mark "rule", :encoding #js {:y #js {:aggregate "mean", :field "precipitation", :type "quantitative"}, :color #js {:value "firebrick"}, :size #js {:value 3}}}]}]
So, they both 'contain' the original map passed in, but the first is [the-map]
, but the second is [some-func, the-map]
And (printchan :EQ? (= (first (rgt/children this)) (second (rgt/argv this)))
==> :EQ? true
Thanks for the insight. The canvas definitely will resize at various points, so keeping it in the component-did-mount and component-did-update may be the best after all. If it is created anew each time, maybe 'did-mount' would be sufficient.
Using https://github.com/reagent-project/reagent/blob/master/examples/material-ui/src/example/core.cljs as base, but not using mui from cljsjs
Suppose I have a function like this:
(defn debounced-input
[initial-props]
(let [debounced-on-change
(goog.functions/debounce (:on-change initial-props)
500)]
(fn [props]
[:input (assoc props :on-change debounced-on-change)])))
The purpose is to debounce on-change
events
This is a Form-2 component, with a debounced-on-change
defined in the outer fn so that changes to the :value
prop etc don't result in the creation of a new debounce-on-change
Now when the on-change
prop updates for whatever reason, the component will re-render (the inner fn wil re-run) but the outer fn won't be re-run, because that's the whole point of a Form-2 comp
Unfortunately that means that changes on on-change
will be dropped on the floow
You may say that on-change does not change typically, but in fact it can happen, especially when it's itself a closure which some value captured in it.
So has anyone found a pattern to deal with this sort of situation?
@pesterhazy I if you wrap the debouncer in another component layer, you can rely on reagent to check the props for equality, only re-debouncing (what a word) when the on-change changes (say that five times fast)
actually you might use the trick that came up yesterday: wrap the debouncer component and force it to remount using a key metadata:
(def on-change (reagent/atom (fn [] "first on-change handler")))
(defn debouncer-component-internal
[oc-orig]
(js/console.log "debouncer-component-internal")
(let [oc oc-orig] ;; debounce here
(fn [_oc-orig]
(js/console.log "debouncer-component-internal render")
[:div {:on-change oc} "foo"])))
(defn debouncer-component
[oc-orig]
(js/console.log "debouncer-component")
^{:key oc-orig} [debouncer-component-internal oc-orig])
(defn test-component
[]
(js/console.log "test-component")
[debouncer-component @on-change])
(js/setTimeout (fn []
(js/console.log "updating on-change")
(reset! on-change (fn [] "second on-change handler")))
1)
This prints:
test-component
debouncer-component
debouncer-component-internal
debouncer-component-internal render
updating on-change
test-component
debouncer-component
debouncer-component-internal
debouncer-component-internal render
yup that might work
here's a solution that I came up with
(defn debouncify
"Given a Reagent component cmp that expects an on-change callback prop idenfied
by handler-kw (often :on-change), return a component that debounces calls
to the callback until input settles down for delay-ms."
[cmp delay-ms handler-kw]
(fn [initial-props]
(let [!handler (atom (handler-kw initial-props))
handler-debounced (gfun/debounce (fn [& args] (apply @!handler args)) delay-ms)]
(r/create-class
{:display-name "Demo"
:component-will-receive-props
(fn [this [_ new-props]]
(reset! !handler (handler-kw new-props)))
:reagent-render
(fn [props & children]
(into [cmp (assoc props handler-kw handler-debounced)] children))}))))
(defn text-input-ui [props]
[:input props])
(def text-input-debounced-ui (debouncify text-input-ui 300 :on-change))
@lee.justin.m forcing a comp to unmount and remount feels a bit against the React spirit
i suppose it is matter of whether you dislike a forced remount or a form-3 component more
honestly I think form-3 components are almost inevitable
honestly i’m not sure which is better, but there is something expressive about the key trick
whenever you rely on a prop in the outer fn, you run the risk of using an out-of-date value when the comp rerenders with a different value for that prop
I created a Gist with a refactored version of the above: https://gist.github.com/pesterhazy/1afd10dda0449121616b54868e3d9452
I guess another question is what should happen when the on-change handler updates but there's an ongoing debounce
(a) cancel the previous debounce or (b) just continue the debounce but with the new handler?
what are you debouncing, out of curiosity. there are lots of input things that don’t handle well to async (as you know)
right, right!
I'm debouncing an input field
using the new adapt-input-component function dreamed up by @juhoteperi and myself: https://github.com/reagent-project/reagent/pull/381/files
this (if I do say so myself) elegantly works around the async issue by using this.setState, which applies synchronously
but if you debounce on-change events in an input field, won’t the later events drop the key presses in the interim?
the way adapt-input-component works is that the on-change handler is replaced with a new one that does two things - setState (which updates the <input> contents immediately) - call the original on-change handler, which is debounced
how does that work if the purpose of the on-change handler is to modify the input before setting a value?
ah right, it doesn't work for that purpose
that's why it's not a perfect drop-in replacement for the current impl
really?? I use controlled input to be able to change the input contents after it has mounted
(instead of having to set the .value
to "" manually when you want to reset a form)
capitalization seems like a fringe use case
well if the user changed the <input> contents after it was mounted, changing defaultValue won't have any effect
i mean to reset a form, you don’t need to set the value in response to an on-change handler
(unless you force-remount using your key trick 🙂 )
how would you reset a search input after submitting?
right
I mean adapt-input-component can be changed to accept another arg, a synchronous transform-fn (by default identity
) which allows capitalization etc.
but I take your point that you can get around some complexity by using defaultValue (if your use case allows for it)
i think allowing another arg would probably solve the problem in a complete way. a more practical use case is, for example, when people auto-format phone numbers and credit card numbers
I guess using defaultValue and setting the .value
of the input manually in a wrapper when a new prop comes in would be another way to implement debouncify...
is it possible to make a better debounce? like, you call “changeable-debouncer”, that returns a different kind of debouncer function: one that will debounce if called using the same function but resets if you pass something new
I think you can implement something like that using an atom and https://google.github.io/closure-library/api/goog.async.Debouncer.html @lee.justin.m
what would the function signature look like? I don't quite understand the idea
@pesterhazy roughly something like this (untested):
(defn changeable-debouncer []
(let [!f [atom nil]
!dbf [atom nil]]
(fn [f &args]
(if (identical? f @!f)
(apply @!dbf args))
(do
(reset! !f f)
(reset! !dbf (debounce f))))))
that's a pretty cool idea
@valtteri I am, yes, and being uncontrolled I don't see any reason why the input won't, well, take any input. Yet that's how it is >_<
@lee.justin.m why do I still have to deal with the transition though?
i mean you still have to figure out policywise what you want to happen when you change target functions
ah right
yeah you may want to dispose of the first debouncer for example
the concept of "wait for things to settle down then go" is deceptively simple
lots of variations
yes definitely. I was thinking of the lodash implementation, for example, which is way more sophisticated
@tomi.hukkalainen_slac show the code, we might be able to help 🙂
@pesterhazy I’ll check that one out, thanks
@pesterhazy did you just @
yourself? 🙂
btw, if you have a moment, does this make any sense to you: https://gist.github.com/jmlsf/7fe736c8b6d7f236361aaad59a1113fd
Long week
@lee.justin.m did you mean did-update?
Did you see https://www.martinklepsch.org/posts/props-children-and-component-lifecycle-in-reagent.html?
i guess you have to use r/props
and r/children
. a bit weird there’s no clean way to get the argv
For an attempt at a better api
Reagent is a bit “funny”
Yeah I agree, a single prop map is better almost always
Maybe time for a simplified reagent v2?!
@pesterhazy funnily enough, props maps was the original way reagent worked looking back on the change history