Skip to content

Instantly share code, notes, and snippets.

@Deraen
Last active April 6, 2018 21:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Deraen/762351365ac04b2057ef5f726342fd1a to your computer and use it in GitHub Desktop.
Save Deraen/762351365ac04b2057ef5f726342fd1a to your computer and use it in GitHub Desktop.
Notes about Reagent and problems with async rendering and controlled inputs

Reagent uses async rendering model. Reagent uses browsers requestAnimationFrame to schedule render calls: https://reagent-project.github.io/news/reagent-is-async.html

Uncontrolled input

[:input {:default-value "foo" :on-change #(js/console.log (.. % -target -value))}]

Uncontroller input is an input which value is not updated by Reagent/React if the value set in the code changes. After initial render, the value is only changed by users interactions.

Controlled input

When the code has to be able to update the value, e.g. by formatting the value or maybe by loading new value from somewhere, Reagent/React has to control the value:

[:input {:value @value :on-change #(reset! value (.. % -target -value))}]

This will cause problems as the value is controller by both application and the user, when the input has focus. Common problems include losing some characters when the application is updating the input to an old value after user written several characters, and cursor jumping to end of the input, when user tries to modify text in start or middle of the input.

Why this works with React

React by default uses synchronous rendering.

TODO: Why does it work?

Solutions

Workaround

Reagent has a workaround for native inputs which changes the inputs into uncontrolled React inputs. Reagent then reimplements the control itself. Reagent keeps track of the inputs DOM value, and the value property set by application, and uses DOM calls to update DOM elements value (and cursor position) when the value property changes.

https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/template.cljs#L160-L207

Custom inputs

Workaround is very hard to use with custom inputs (like MaterialUI TextField). Problem is that due to how workaround makes the input uncontrolled and then manually tries to update the state, custom inputs can't keep track of the input. It is easy to update the DOM value, but e.g. TextField needs to know the current value to control styles of the input (is it empty, where to place the floating label etc.) With custom inputs, in addition to updating DOM value, this logic of custom inputs would need to be duplicated.

To update the state of custom React component, we'd need the React component instance so it would be possible to call setState or other methods of the component. Only way in React for parent components (e.g. component in Reagent wrapping the custom input) to get reference to children is :ref. This works work MaterialUI 0. But in MaterialUI 1 the TextField is implemented as Stateless Functional Component, which doesn't support ref.

How do others solve this?

Some related issues

AFAIK these don't solve custom inputs.

reagent-project/reagent#253 tonsky/rum#86 r0man/sablono#148 https://github.com/omcljs/om/blob/master/src/main/om/dom.cljs#L20 (seems simpler) facebook/react#7027

Official React async support

https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html

React is going to support async rendering soonish. Maybe they will have to solve the same problem.

Foo

It might be that the problem is so apparent with Reagent, because Reagent directs users to update state (RAtom) on each on-change event, and then update value on every change. If state was only updated when input loses focus (on-blur, or something), controlled input wouldn't need to control DOM value at the same time as user modifies the value. This would prevent lost characters and moving cursor.

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