Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controlled input loses cursor with React 18 when input type is :email or :number #609

Open
flyingmachine opened this issue Sep 1, 2024 · 3 comments

Comments

@flyingmachine
Copy link

Inputs of type :email and :number exhibit the controlled-input cursor bug: as you type, the cursor will always reappear at the end end of the input. Inputs of type :text and :password don't exhibit this behavior.

Little repo to repro: https://github.com/flyingmachine/reagent-react-18-controlled-input

(ns main.app
  (:require
   ["react" :as react]
   ["react-dom/client" :refer [createRoot]]
   [goog.dom :as gdom]
   [reagent.core :as r]
   [reagent.dom.client :as client]))

(defonce root (createRoot  (gdom/getElement "app")))

(defn text []
  (r/with-let [text (r/atom "")]
    [:input {:type :text
             :on-change #(reset! text (.. % -target -value))
             :value @text}]))

(defn email []
  (r/with-let [text (r/atom "")]
    [:input {:type :email
             :on-change #(reset! text (.. % -target -value))
             :value @text}]))

(defn number []
  (r/with-let [text (r/atom "")]
    [:input {:type :number
             :on-change #(reset! text (.. % -target -value))
             :value @text}]))

(defn app
  []
  [:table
   [:tbody
    [:tr [:td "text"] [:td [text]]]
    [:tr [:td "email"] [:td [email]]]
    [:tr [:td "number"] [:td [number]]]]])

(defn ^:dev/after-load start []
  (client/render root [app]))

(defn init
  []
  (start))
@mike-thompson-day8
Copy link
Member

mike-thompson-day8 commented Sep 1, 2024

I originally implemented this solution for input fields:

;; Setting "value" (below) moves the cursor position to the
;; end which gives the user a jarring experience.
;;
;; But repositioning the cursor within the text, turns out to
;; be quite a challenge because changes in the text can be
;; triggered by various events like:
;; - a validation function rejecting a user inputted char
;; - the user enters a lower case char, but is transformed to
;; upper.
;; - the user selects multiple chars and deletes text
;; - the user pastes in multiple chars, and some of them are
;; rejected by a validator.
;; - the user selects multiple chars and then types in a
;; single new char to repalce them all.
;; Coming up with a sane cursor repositioning strategy hasn't
;; been easy ALTHOUGH in the end, it kinda fell out nicely,
;; and it appears to sanely handle all the cases we could
;; think of.
;; So this is just a warning. The code below is simple
;; enough, but if you are tempted to change it, be aware of
;; all the scenarios you have handle.
(let [node-value (.-value node)]
(if (not= node-value dom-value)
;; IE has not notified us of the change yet, so check again later.
;; Issue #566: Also setup force flag, so that cljsDOMValue will be set
;; to dom elements current value, even if the input is activeElement.
;; This fixes cases where input is focused from the code, before React
;; render is called and input-component-set-value and
;; input-node-set-value would be called infinitely.
(batch/do-after-render #(binding [*force-set-dom-value* true]
(input-component-set-value component)))
(let [existing-offset-from-end (- (count node-value)
(.-selectionStart node))
new-cursor-offset (- (count rendered-value)
existing-offset-from-end)]
(set! (.-cljsDOMValue component) rendered-value)
(set! (.-value node) rendered-value)
(when (fn? on-write)
(on-write rendered-value))
(set! (.-selectionStart node) new-cursor-offset)
(set! (.-selectionEnd node) new-cursor-offset))))))

But it relies on selection API being present on the input:

(def these-inputs-have-selection-api #{"text" "textarea" "password" "search"
"tel" "url"})

I think email can easily be added to that list because these days it supports the necessary API. (Maybe it always did).

But it doesn't appear as if number can be added - so that would have to be solved another way, probably with much more effort.

@Deraen Deraen added the bug label Sep 2, 2024
@Deraen
Copy link
Member

Deraen commented Sep 2, 2024

Fixing for email type seems simple enough.

Related: #504
Removing the rAF scheduling would remove the need for input workaround, but it would also be a big breaking change in Reagent so not sure if it going to happen ever. You could use UIx/Helix instead.

@Deraen
Copy link
Member

Deraen commented Sep 19, 2024

Update to previous: fixing email isn't any simpler than other cases, browser doesn't provide the necessary Selection API methods for those inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants