Yep, I've seen it as well in my own attempt to understand what was wrong. No clue, sorry.
Let me know if you find a solution.
Hi, is this the recommended way of rendering a minutes since component?
(defn mins-since [start-time]
(let [mins (r/atom (mins start-time))]
(fn [start-time]
(js/setTimeout #(swap! mins inc) 60000)
[:span @mins])))
IIRC the render function may potentially be called multiple times, so I wouldn't do it like that. Reagent has a very similar example right on its landing page: https://reagent-project.github.io/
Search for the words "Hello world, it is now".
Thought so, is with-let
a better use here?
If you really want to avoid having a global counter, then I think you need to create a proper class component, use setInterval
, and dispose of that interval once the component is unmounted.
I got it working like this
:renderInput (fn [props] (r/create-element Textfield props))
Where Textfield
is a native React component imported like this ["@material-ui/core/Textfield" :default Textfield]
(shadow-cljs)@valtteri Do you have any idea why as-element
does not work, what it does "wrong"?
First I dabbled with as-element
and reactify-component
but it turned out to be simpler to just not jump from ‘react-world’ into ‘reagent-world’
I’m not sure, but my best guess is that props
is a js-object and you need to clj->js
it and it looses the ref there for some reason.
Maybe @juhoteperi could give us the proper answer 🙂
I got it to render with (r/as-element [:> Textfield (js->clj props)])
but ref was lost or something and it died when trying to open the select list.
Instead of yourself counting the minutes, trigger update every x seconds, and calculate difference between start and end timestamps.
Huh, yeah.
With reactify-component
(.. props -inputProps -ref -current)
is the <input>
element. And with as-element
it somehow becomes null
. Even before we return from the function.
https://github.com/metosin/komponentit/blob/master/src/cljs/komponentit/timeago.cljs#L49 this is maybe unnecessarily too complex, but might give some idea. with-let
is probably good way to create and destry interval.
(It uses timeout instead of interval to optimize how often it is called, based on the previous difference.)
Nothing obviously wrong here. hmh.
You can try (r/reactify-component (fn [props] [:> TextField ...]))
That would be the correct way to use reactify-component, give the Reagent component as the parameter, not the elements vector.
Will it work, even though props
are expected to be a JS object?
Hmm, probably not. Bare function and as-element
should be best way here, as it doesn't try converting props.
Or hm, as-element also might do some conversion.
You could try (fn [props] (r/create-element TextField props))
yes that's it probably. as-element
will do some props conversion, but create-element
is just the React createElement
call
What conversions does as-element
do?
Oh, right... You cannot convert props at all if you're using react/createRef
. Because that mechanism expects that the underlying JS object is mutable.
@valtteri It does some custom version of clj->js
. And we also do the regular js->clj
. Thus, the ref object is not the same. Any mutation to it is not visible to the component that has set the ref in the first place.
Thanks guys for the explanations!
as-element
and reactify-component
do the same clj props map to js props map conversion, it might have some inference with Classes it doesn't know about
https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/template.cljs#L62-L69 the first check probably only works for Plain Old Objects
But even if clj->js
is called for the ref value, it doesn't change it
@juhoteperi It's not about changing the value, it's about changing the holding object. TextField
is expected to call something like ref.current = this
in the depths of React. And if ref
is a new object created by clj->js
or the like, the Autocomplete
component will never see the change.
I might convert Reagent material-ui example to Shadow-cljs later and investigate more. Currently I don't have project with material-ui/labs running.
(let [a (react/createRef)]
(js/console.log (identical? a (clj->js a))))
This prints true
, ref object should be the same object still
To put it in other words - the above example will not work in raw React if you do something like props = deepCopy(props)
.
But if there is JS props -> clj props map conversion somewhere, that could do something else
I got test coverage reports working with Cljs tests: https://github.com/reagent-project/reagent/pull/484 🎉
1👏Good point - as-element
doesn't work even without js->clj
.
Oh, duh - it doesn't work because it's not supposed to work with plain objects. It turns props
into {children: props}
.
I think, the case can be closed. :)
I agree, that's a better approach. Thanks
So if you use js->clj
that will definitely convert ref object to Cljs map, which break it
Autocomplete expects renderInput to be function returning React elements, not component. That might explain why reactify-component doesn't work, it returns class component.
Thx a lot for figuring this out.
How should I translate startAdornment in the following React code into Reagent?
<TextField
className={classes.margin}
id="input-with-icon-textfield"
label="TextField"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
/>
I was trying to have to have something like
[:> TextField {:input-props {:startAdornment (r/create-element InputAdornment #js {:position "start"} "Test")}}]
But I am not getting anything rendered.Also the following does not work
(r/as-element [:> InputAdornment {:position "start"} "Test"])
From API documentation, startAdornment should be a node: https://material-ui.com/api/input/
inputProps
!= InputProps
. Stupid naming IMO, given that inputProps
are actually attributes.
:input-props
is translated into inputProps
. And you need :InputProps
.
Ya, thx.
I was confused since read-only property was working with both inputProps and InputProps, so I didn't consider that there is a problem with this.