Fork me on GitHub
#reagent
<
2020-03-21
>
wimomisterx10:03:30

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-himik11:03:27

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.

👍 4
wimomisterx11:03:20

awesome thanks mate!

Pavel Klavík19:03:48

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-himik19:03:09

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

Pavel Klavík20:03:57

Thx, that helped. What is the difference?

p-himik20:03:23

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.

Pavel Klavík20:03:39

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

Pavel Klavík20:03:39

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

Pavel Klavík20:03:39

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

p-himik09:03:48

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

p-himik09:03:58

Let me know if you find a solution.

valtteri14:03:52

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-himik14:03:46

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

valtteri14:03:55

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’

valtteri14:03:07

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.

valtteri14:03:35

Maybe @U061V0GG2 could give us the proper answer 🙂

valtteri14:03:59

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-himik14:03:30

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.

juhoteperi14:03:08

Nothing obviously wrong here. hmh.

juhoteperi14:03:01

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-himik14:03:36

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

juhoteperi14:03:32

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

juhoteperi14:03:51

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

juhoteperi14:03:08

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

juhoteperi14:03:38

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

valtteri14:03:38

What conversions does as-element do?

p-himik14:03:00

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-himik14:03:46

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

👌 4
valtteri14:03:25

Thanks guys for the explanations!

juhoteperi14:03:04

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

juhoteperi14:03:44

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

p-himik14:03:16

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

juhoteperi14:03:47

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.

juhoteperi14:03:34

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

p-himik14:03:54

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

juhoteperi14:03:54

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

p-himik14:03:37

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

p-himik14:03:03

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

juhoteperi16:03:33

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

juhoteperi17:03:04

Autocomplete expects renderInput to be function returning React elements, not component. That might explain why reactify-component doesn't work, it returns class component.

Pavel Klavík18:03:34

Thx a lot for figuring this out.