diff --git a/doc/ControlledInputs.md b/doc/ControlledInputs.md new file mode 100644 index 0000000..7ee872b --- /dev/null +++ b/doc/ControlledInputs.md @@ -0,0 +1,30 @@ +# Controlled inputs + +Reagent uses async rendering which cause problems with controlled inputs. If +the input element is created directly by Reagent (i.e. `[:input ...]` in hiccup), [a +workaround](https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/template.cljs#L132-L238) +can be applied, but if the input is created by JS library (i.e. JSX `` +or React `create-element`), Reagent doesn't see +the element so the workaround can't be applied. + +Due to async rendering, the DOM update doesn't occur during the event handler, +but some time later. In certain cases, like when the cursor is not at the end +of the input, updating the DOM input value causes the cursor to move to the +end of the input. Without async rendering, browsers probably implement logic +to keep the cursor position if the value is updated during event handler. + +Reagent workaround works by changing the React input element into +uncontrolled input (i.e. the DOM value is not updated by React). Instead +Reagent will update DOM itself if the Reagent input value property changes. +This enables Reagent to check the cursor position before updating the +value, and if needed, save and restore the cursor position +after updating the value. + +For JS libraries, usually the best solution is if the library provides an option to +use custom component to create the input element, which enables +Reagent to create the input element: + +## Examples + +- [Material UI](./examples/material-ui.md) +- [Smooth UI](./examples/smooth-ui.md) diff --git a/doc/cljdoc.edn b/doc/cljdoc.edn index 03fece6..8803fe3 100644 --- a/doc/cljdoc.edn +++ b/doc/cljdoc.edn @@ -8,7 +8,8 @@ ["[WIP] Managing State: atoms, cursors, Reactions, and tracking" {:file "doc/ManagingState.md"}] ["Batching and Timing: How Reagent Renders Changes to Application State" {:file "doc/BatchingAndTiming.md"}] ["Interop with React" {:file "doc/InteropWithReact.md"}] - ["React Features" {:file "doc/ReactFeatures.md"}]] + ["React Features" {:file "doc/ReactFeatures.md"}] + ["Controlled Inputs" {:file "doc/ControlledInputs.md"}]] ["Frequently Asked Questions" {} ["Why isn't my Component re-rendering?" {:file "doc/FAQ/ComponentNotRerendering.md"}] ["How do I use React's \"refs\"" {:file "doc/FAQ/UsingRefs.md"}] diff --git a/doc/examples/smooth-ui.md b/doc/examples/smooth-ui.md new file mode 100644 index 0000000..d5c68e2 --- /dev/null +++ b/doc/examples/smooth-ui.md @@ -0,0 +1,20 @@ +# Smooth UI + +Smooth UI has the same problem with [controlled inputs](../CotrolledInputs.md) +as [Material UI](./material-ui.md). +The problem can be solved by providing custom component to Smooth UI inputs +which will create the Input element using Reagent, enabling Reagent to use +it's workaround logic to control input value and cursor position: + +```cljs +(def r-input (r/reactify-component + (fn [props] + ;; Omit: + ; https://github.com/smooth-code/smooth-ui/blob/c5f3c75a438a04e766dbedeafc2be54252a5338e/packages/shared/core/createComponent.js#L31 + ; https://github.com/smooth-code/ smooth-ui/blob/c5f3c75a438a04e766dbedeafc2be54252a5338e/packages/shared/core/Input.js#L84 + ;; Maybe also: + ; (.. system -meta -props) + [:input (dissoc props :__scTheme :theme :control :size :valid)]))) + +(r/render [:> Input {:as r-input ...}] container) +```