reagent

A minimalistic ClojureScript interface to React.js http://reagent-project.github.io/
wimomisterx 2020-03-21T10:58:30.201700Z

Hello, is there a better way of filtering a list in a reaction , is it okay to use doall ?

(defn my-component []
  (let [foo (r/atom [1 2 3])
        bar (r/atom 2)
        baz (reaction (doall (filter #(> % @bar) @foo))) ]
   (fn []
     ....)) 

p-himik 2020-03-21T11:46:27.201900Z

doall should be fine. You can also use (into [] (filter ...) @foo) if you need a vector - it uses a transducer, so it should be more efficient.

👍 1
wimomisterx 2020-03-21T11:47:20.202200Z

awesome thanks mate!

2020-03-21T19:10:48.204100Z

Hi, I am trying to wrap Autocomplete component from Material-ui in Reagent: https://material-ui.com/components/autocomplete/. Here is their example code:

<Autocomplete
        id="free-solo-demo"
        freeSolo
        options={top100Films.map(option => option.title)}
        renderInput={params => (
          <TextField {...params} label="freeSolo" margin="normal" variant="outlined" />
        )}
      />
I am wrapping this like this:
[:> Autocomplete {:options        options
                    :getOptionLabel get-option-label
                    :renderOption   render-option
                    :freeSolo       true
                    :renderInput    (fn [params]
                                      (r/reactify-component [:> TextField (assoc (js->clj params)
                                                                            :label label
                                                                            :variant "outlined"
                                                                            :on-change on-change)]
In particular, I am not sure how renderInput should be wrapped. It does not display any error but the component is empty, not showing anything.

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.

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.

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

👌 1
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

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

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

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

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.

2020-03-22T18:58:34.218400Z

Thx a lot for figuring this out.

p-himik 2020-03-21T19:58:09.204300Z

Try to replace r/rectify-component with r/as-element.

2020-03-21T20:03:57.204500Z

Thx, that helped. What is the difference?

p-himik 2020-03-21T20:05:23.204700Z

Docstrings to the rescue. :) The first one is to convert Reagent components (i.e. CLJS functions). The second one is to convert Hiccup to React elements.

2020-03-21T20:06:27.204900Z

I see.

2020-03-21T20:28:39.205100Z

It still fails when I try to click on the text field:

2020-03-21T20:28:39.205300Z

useAutocomplete.js:180 Uncaught TypeError: Cannot read property 'removeAttribute' of null
    at useAutocomplete.js:180
    at useEventCallback.js:26
    at useAutocomplete.js:400
    at useEventCallback.js:26
    at useAutocomplete.js:426
    at commitHookEffectListMount (react-dom.development.js:19765)
    at commitPassiveHookEffects (react-dom.development.js:19803)
    at HTMLUnknownElement.callCallback (react-dom.development.js:189)
    at Object.invokeGuardedCallbackImpl (react-dom.development.js:238)
    at invokeGuardedCallback (react-dom.development.js:293)
(anonymous) @ useAutocomplete.js:180
(anonymous) @ useEventCallback.js:26
(anonymous) @ useAutocomplete.js:400
(anonymous) @ useEventCallback.js:26
(anonymous) @ useAutocomplete.js:426
commitHookEffectListMount @ react-dom.development.js:19765
commitPassiveHookEffects @ react-dom.development.js:19803
callCallback @ react-dom.development.js:189
invokeGuardedCallbackImpl @ react-dom.development.js:238
invokeGuardedCallback @ react-dom.development.js:293
flushPassiveEffectsImpl @ react-dom.development.js:22885
exports.unstable_runWithPriority @ scheduler.development.js:654
runWithPriority$1 @ react-dom.development.js:11062
flushPassiveEffects @ react-dom.development.js:22852
performSyncWorkOnRoot @ react-dom.development.js:21769
(anonymous) @ react-dom.development.js:11112
exports.unstable_runWithPriority @ scheduler.development.js:654
runWithPriority$1 @ react-dom.development.js:11062
flushSyncCallbackQueueImpl @ react-dom.development.js:11107
flushSyncCallbackQueue @ react-dom.development.js:11095
Internals.Events @ react-dom.development.js:21925
dispatchDiscreteEvent @ react-dom.development.js:1072
react_devtools_backend.js:6 The above error occurred in the <ForwardRef> component:
    in ForwardRef (created by WithStyles(ForwardRef))
    in WithStyles(ForwardRef) (created by orgpad.client.views.widgets.suggestions.suggestions)
    in orgpad.client.views.widgets.suggestions.suggestions (created by orgpad.client.views.share_orgpage.users)
    in orgpad.client.views.share_orgpage.users (created by orgpad.client.views.share_orgpage.share_orgpage_dialog)
    in orgpad.client.views.share_orgpage.share_orgpage_dialog (created by orgpad.client.views.widgets.md_dialog.md_dialog)
    in div (created by ForwardRef)
    in ForwardRef (created by WithStyles(ForwardRef))
    in WithStyles(ForwardRef) (created by orgpad.client.views.widgets.md_dialog.md_dialog)
    in div (created by ForwardRef)
    in ForwardRef (created by WithStyles(ForwardRef))
    in WithStyles(ForwardRef) (created by ForwardRef)
    in div (created by Transition)
    in Transition (created by ForwardRef)
    in ForwardRef (created by TrapFocus)
    in TrapFocus (created by ForwardRef)
    in div (created by ForwardRef)
    in ForwardRef (created by ForwardRef)
    in ForwardRef (created by ForwardRef)
    in ForwardRef (created by WithStyles(ForwardRef))
    in WithStyles(ForwardRef) (created by orgpad.client.views.widgets.md_dialog.md_dialog)
    in orgpad.client.views.widgets.md_dialog.md_dialog (created by orgpad.client.views.share_orgpage.share_orgpage)
    in orgpad.client.views.share_orgpage.share_orgpage (created by orgpad.client.views.root.modal_dialogs)
    in orgpad.client.views.root.modal_dialogs (created by root-component)
    in div (created by root-component)
    in div (created by root-component)
    in root-component (created by orgpad.client.views.root.root)
    in orgpad.client.views.root.root

Consider adding an error boundary to your tree to customize error handling behavior.
Visit <https://fb.me/react-error-boundaries> to learn more about error boundaries.
r @ react_devtools_backend.js:6
logCapturedError @ react-dom.development.js:19561
logError @ react-dom.development.js:19598
expirationTime.callback @ react-dom.development.js:20742
commitUpdateQueue @ react-dom.development.js:12513
commitLifeCycles @ react-dom.development.js:19917
commitLayoutEffects @ react-dom.development.js:22835
callCallback @ react-dom.development.js:189
invokeGuardedCallbackImpl @ react-dom.development.js:238
invokeGuardedCallback @ react-dom.development.js:293
commitRootImpl @ react-dom.development.js:22573
exports.unstable_runWithPriority @ scheduler.development.js:654
runWithPriority$1 @ react-dom.development.js:11062
commitRoot @ react-dom.development.js:22413
performSyncWorkOnRoot @ react-dom.development.js:21839
(anonymous) @ react-dom.development.js:11112
exports.unstable_runWithPriority @ scheduler.development.js:654
runWithPriority$1 @ react-dom.development.js:11062
flushSyncCallbackQueueImpl @ react-dom.development.js:11107
flushSyncCallbackQueue @ react-dom.development.js:11095
Internals.Events @ react-dom.development.js:21925
dispatchDiscreteEvent @ react-dom.development.js:1072
react-dom.development.js:11125 Uncaught TypeError: Cannot read property 'removeAttribute' of null
    at useAutocomplete.js:180
    at useEventCallback.js:26
    at useAutocomplete.js:400
    at useEventCallback.js:26
    at useAutocomplete.js:426
    at commitHookEffectListMount (react-dom.development.js:19765)
    at commitPassiveHookEffects (react-dom.development.js:19803)
    at HTMLUnknownElement.callCallback (react-dom.development.js:189)
    at Object.invokeGuardedCallbackImpl (react-dom.development.js:238)
    at invokeGuardedCallback (react-dom.development.js:293)

2020-03-21T20:28:39.205500Z

Not sure whether the transformation I do on params is correct.