reagent-project / reagent

A minimalistic ClojureScript interface to React.js
http://reagent-project.github.io/
MIT License
4.76k stars 414 forks source link

Infinite loop in the reagent.impl.input wrapper #566

Closed roman01la closed 2 years ago

roman01la commented 2 years ago

Input fields wrapper in reagent.impl.input ns gets into an infinite loop when an input field goes in focus immediately after it was set a value. Note that the loop is async, thus it doesn't block UI thread. The issue is reproducible for any type of component: class-based with r/atom state, functional with r/atom state, functional with hooks state.

Repro

;; intercepting for debugging purpose
(defonce f reagent.impl.input/input-node-set-value)
(set! reagent.impl.input/input-node-set-value
      (fn [& args]
        (js/console.log "RUN")
        (apply f args)))

(def input-ref #js {:current nil})
(def query (r/atom nil))

(defn app []
  [:div
   [:input {:on-change #(reset! query (.. % -target -value))
            :ref input-ref
            :value @query}]
   [:button {:on-click #(do (reset! query "HELLO")
                            (.focus ^js/HTMLElement (.-current input-ref)))
             :name "tidy-new"
             :class "large"}
    "set value"]])

(reagent.dom/render [app] (js/document.getElementById "root"))
Deraen commented 2 years ago

If the input has focus, .-activeElement check in input-node-set-value is always true and input-component-set-value and input-node-set-value keep calling each other waiting on-change handler to update .-cljsDOMValue, which doesn't happen here as the update is triggered from code.

I wrote a PR to avoid this by forcing input-node-set-value to update cljsDOMValue when it is called after render, but I'm not sure if this is a correct fix.

Other workaround could be to listen for focus events and try to detect if the event is real or send from user code, but that would probably be quite unreliable.