Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem with Autocomplete (Material UI + React + Reagent/ClojureScript)

I have a problem using the Material UI's Autocomplete with Reagent (ClojureScript). The element renders fine, but when I try to click on it, I get the following exceptions:

Uncaught TypeError: Cannot read property 'focus' of null
    at handleClick (useAutocomplete.js:938)
    at HTMLUnknownElement.callCallback (react-dom.development.js:189)
    at Object.invokeGuardedCallbackImpl (react-dom.development.js:238)
    at invokeGuardedCallback (react-dom.development.js:293)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:307)
    at executeDispatch (react-dom.development.js:390)
    at executeDispatchesAndReleaseTopLevel (react-dom.development.js:412)
    at forEachAccumulated (react-dom.development.js:3260)
    at runEventsInBatch (react-dom.development.js:3305)
    at handleTopLevel (react-dom.development.js:3515)

useAutocomplete.js:322 Uncaught TypeError: Cannot read property 'removeAttribute' of null
    at eval (useAutocomplete.js:322)
    at eval (useEventCallback.js:26)
    at eval (useAutocomplete.js:433)
    at eval (useEventCallback.js:26)
    at eval (useAutocomplete.js:463)
    at eval (useAutocomplete.js:528)
    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)

Breaking in JS debugger, I see that inputRef.current is null (which is what focus and removeAttribute are called on. (Oddly enough, the only place where inputRef is set in the file is by calling useRef(null), which is what results in inputRef.current being null.)

In my code, I define the Autocomplete field as follows:

(ns my-product-redacted.views.atoms
  (:require [reagent.core :as r]
            ["@material-ui/lab/Autocomplete" :default Autocomplete]
            ;; other requires
  ))

(def autocomplete-field (r/adapt-react-class Autocomplete))

Then, in a React component, it is used as follows:

[a/autocomplete-field {:render-input      (fn [js-params]
                                            (let [clj-params (js->clj js-params)
                                                  params     {:label             label
                                                              :width             width
                                                              :select            select?
                                                              :Input-label-props {:shrink true}
                                                              :Select-props      {:native true}}
                                                  all-params  (into clj-params params)]
                                               (js/console.log (clj->js all-params))
                                               (r/as-element [a/text-field all-params])))
                       :options          (when select? (cons {:value "" :label ""} options))
                       :get-option-label (fn [option] (or (get (js->clj option) "label") ""))
                       :default-value    (when (not select?) value-override)
                       :value            (when select? value)
                       :disabled         disabled?
                       :on-focus         #(re-frame/dispatch [::forms/on-focus path])
                       :on-blur          #(re-frame/dispatch [::forms/on-blur path])
                       :on-change        #(re-frame/dispatch (conj on-change (-> % .-target .-value)))})]

(Here, a/text-field is also defined in the same namespace as a/autocomplete-field and in a similar way.)

The JS console log (from the (js/console.log (clj->js params)) call) shows that inputProps.ref.current is set to null. However, InputProps.ref is not null. Even so, I tried to manually associate the same function as is passed with InputProps.ref to inputProps.ref.current, but that made no difference.

I have also tried the workaround suggested in https://github.com/mui-org/material-ui/issues/21245 (although that issue is with the Gestalt library, not with Reagent, it suggests that there may be a problem with ref forwarding). But wrapping the text-field into a div with the ref taken from InputProps.ref also made no difference.

Any suggestions?

like image 575
silverberry Avatar asked Sep 17 '20 18:09

silverberry


1 Answers

Calling js->clj on the render-input js params breaks them.

I put a quick demo of the material UI autocomplete component in my demo repository here.

But it's mainly taken from the official reagent docs/examples here:

(defn autocomplete-example []
  [:> mui/Grid
   {:item true}
   [:> Autocomplete {:options ["foo" "bar" "foobar"]
                     :style {:width 300}
                     ;; Note that the function parameter is a JS Object!
                     ;; Autocomplete expects the renderInput value to be function
                     ;; returning React elements, not a component!
                     ;; So reactify-component won't work here.
                     :render-input (fn [^js params]
                                     ;; Don't call js->clj because that would recursively
                                     ;; convert all JS objects (e.g. React ref objects)
                                     ;; to Cljs maps, which breaks them, even when converted back to JS.
                                     ;; Best thing is to use r/create-element and
                                     ;; pass the JS params to it.
                                     ;; If necessary, use JS interop to modify params.
                                     (set! (.-variant params) "outlined")
                                     (set! (.-label params) "Autocomplete")
                                     (r/create-element mui/TextField params))}]])
like image 185
dakra Avatar answered Nov 14 '22 17:11

dakra