A minimalistic ClojureScript interface to React.js
p-himik 2020-03-22T09:30:48.205700Z

Yep, I've seen it as well in my own attempt to understand what was wrong. No clue, sorry.

p-himik 2020-03-22T09:30:58.205900Z

Let me know if you find a solution.

wimomisterx 2020-03-22T11:20:45.208600Z

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])))

p-himik 2020-03-22T11:53:42.208700Z

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:

p-himik 2020-03-22T11:53:57.208900Z

Search for the words "Hello world, it is now".

wimomisterx 2020-03-22T12:48:53.209100Z

Thought so, is with-let a better use here?

p-himik 2020-03-22T12:53:50.209300Z

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.

valtteri 2020-03-22T14:16:52.209500Z

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)

p-himik 2020-03-22T14:18:46.209700Z

@valtteri Do you have any idea why as-element does not work, what it does "wrong"?

valtteri 2020-03-22T14:18:55.209900Z

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’

valtteri 2020-03-22T14:20:07.210100Z

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.

valtteri 2020-03-22T14:20:35.210300Z

Maybe @juhoteperi could give us the proper answer 🙂

valtteri 2020-03-22T14:22:59.210500Z

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.

juhoteperi 2020-03-22T14:31:08.210700Z

Instead of yourself counting the minutes, trigger update every x seconds, and calculate difference between start and end timestamps.

p-himik 2020-03-22T14:32:30.210900Z

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.

juhoteperi 2020-03-22T14:33:12.211100Z this is maybe unnecessarily too complex, but might give some idea. with-let is probably good way to create and destry interval.

juhoteperi 2020-03-22T14:34:37.211500Z

(It uses timeout instead of interval to optimize how often it is called, based on the previous difference.)

juhoteperi 2020-03-22T14:35:08.211700Z

Nothing obviously wrong here. hmh.

juhoteperi 2020-03-22T14:36:01.212100Z

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.

p-himik 2020-03-22T14:36:36.212300Z

Will it work, even though props are expected to be a JS object?

juhoteperi 2020-03-22T14:37:32.212500Z

Hmm, probably not. Bare function and as-element should be best way here, as it doesn't try converting props.

juhoteperi 2020-03-22T14:37:51.212700Z

Or hm, as-element also might do some conversion.

juhoteperi 2020-03-22T14:38:08.212900Z

You could try (fn [props] (r/create-element TextField props))

juhoteperi 2020-03-22T14:38:38.213100Z

yes that's it probably. as-element will do some props conversion, but create-element is just the React createElement call

valtteri 2020-03-22T14:42:38.213300Z

What conversions does as-element do?

p-himik 2020-03-22T14:43:00.213500Z

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.

p-himik 2020-03-22T14:43:46.213700Z

@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.

valtteri 2020-03-22T14:44:25.214Z

Thanks guys for the explanations!

juhoteperi 2020-03-22T14:47:04.214200Z

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

juhoteperi 2020-03-22T14:47:53.214400Z the first check probably only works for Plain Old Objects

juhoteperi 2020-03-22T14:50:44.214700Z

But even if clj->js is called for the ref value, it doesn't change it

p-himik 2020-03-22T14:52:16.214900Z

@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.

juhoteperi 2020-03-22T14:52:47.215100Z

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.

juhoteperi 2020-03-22T14:53:34.215300Z

(let [a (react/createRef)] (js/console.log (identical? a (clj->js a)))) This prints true, ref object should be the same object still

p-himik 2020-03-22T14:53:54.215500Z

To put it in other words - the above example will not work in raw React if you do something like props = deepCopy(props).

juhoteperi 2020-03-22T14:53:54.215700Z

But if there is JS props -> clj props map conversion somewhere, that could do something else

juhoteperi 2020-03-22T14:55:43.216300Z

I got test coverage reports working with Cljs tests: 🎉

p-himik 2020-03-22T14:57:37.216500Z

Good point - as-element doesn't work even without js->clj.

p-himik 2020-03-22T14:59:03.216700Z

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. :)

wimomisterx 2020-03-22T15:33:50.216900Z

I agree, that's a better approach. Thanks

juhoteperi 2020-03-22T16:49:33.217200Z

So if you use js->clj that will definitely convert ref object to Cljs map, which break it

juhoteperi 2020-03-22T17:03:04.217900Z

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?

          startAdornment: (
            <InputAdornment position="start">
              <AccountCircle />


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:

p-himik 2020-03-22T19:17:09.221600Z

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.