Fork me on GitHub
#reagent
<
2018-06-17
>
fabrao15:06:49

@juhoteperi using

<!-- Polyfills -->
    <!--[if lt IE 10]>
    <script src=""></script>
    <![endif]-->
    <script src=""></script>
solve the problem

fabrao15:06:19

Many thanks

juhoteperi17:06:34

Examples directory contains now official example of using MaterialUI v1 with working TextField! https://github.com/reagent-project/reagent/blob/master/examples/material-ui/src/example/core.cljs

gadfly36117:06:13

๐Ÿ‘€ woohoo!!

pesterhazy19:06:42

@juhoteperi thanks for putting up the example, that's very useful

pesterhazy19:06:21

So your solution is to replace the <input> in material-ui with a custom component, correct?

pesterhazy19:06:21

Did you experiment with other approaches that do not rely on the 3rd party supplying a hook to swap in a custom <input> renderer?

juhoteperi19:06:04

Yes. Being able to use Reagent :input at the lowest level enables getting the proper Reagent cursor fixes without need to reimplement any Material-UI logic.

juhoteperi19:06:35

I'm not aware of other fixes that would work properly.

valtteri19:06:25

I wasted today ~6 hours with different experiments. ๐Ÿ˜„

pesterhazy19:06:29

Unfortunately this won't necessarily work with every other 3rd party styled input component

pesterhazy19:06:02

@valtteri sounds like "reagent+input" is on collective our minds ๐Ÿ™‚

juhoteperi19:06:16

People should ask other libs to provide similar option. I'm thinking we might have to do that for Material-UI Input/Textarea so we can get the auto height textarea fixed.

valtteri19:06:18

Or well, not wasted since I came up with something that @juhoteperi could polish into a nice example + docs. ๐Ÿ™‚

pesterhazy19:06:19

I hadn't thought of this option

pesterhazy19:06:42

@valtteri what did you find?

valtteri19:06:19

That material-ui v1 provides the possibility to slip in your own input component

juhoteperi19:06:34

This fix. I just copied the idea and wrote docs ๐Ÿ™‚

pesterhazy19:06:37

It's a vexing problem. I think there must be a more general solution out there, but I struggle to find it

pesterhazy19:06:18

Ah, good job @valtteri ๐Ÿ™‚

juhoteperi19:06:38

I think this is The Solution. Passing component as property is very React way to customize logic inside custom components, which is what we need here.

pesterhazy19:06:58

Agree that passing components is very common

valtteri19:06:29

I was already quite desperate with hacky uncontrolled inputs and other not-so-good alternatives.

pesterhazy19:06:48

I'm not sure it would work with the ReactNative input components for example

pesterhazy19:06:14

@valtteri what would you say is the core of the problem?

valtteri19:06:26

Iโ€™m not yet so deep into react + reagent that I could say whatโ€™s the core.

pesterhazy19:06:07

I guess the answer must include (a) Ratoms and (b) asynchronous rendering

pesterhazy19:06:49

To prevent problems, the on-change event needs to update the "value" prop in the same event loop tick

pesterhazy19:06:08

But that's out of the question if a Ratom is used

pesterhazy19:06:23

(That's my understanding of the problem)

valtteri19:06:35

Yeah Iโ€™ve read also something like that

juhoteperi19:06:50

Ratoms aren't directly a cause. Async rendering alone with controlled inputs is enough to cause problems which require the logic in Reagent to make inputs uncontrolled + manually update the values. Though ratoms are reason why async rendering is good idea.

pesterhazy19:06:06

So one solution could be a wrapper component that updates the value immediately and somehow also communicates with a Ratom

pesterhazy19:06:50

The wrapper component could be implemented by calling r/create-class directly

juhoteperi19:06:52

I think the problem could be reproduced in Reagent without Ratoms, with just React state.

pesterhazy19:06:51

Right, but I think the argument works from the other direction. Because we want the user to use a Reaction as a :value prop for input components, we need to use async rendering; and that causes problems with inputs.

pesterhazy20:06:57

So my idea was to "decouple" an input in a wrapper comp that avoids requestAnimationFrame

juhoteperi20:06:07

Async rendering can't be turned off per component. And "Updates the value immediately" is pretty much what the input fix in Reagent does, but way to enable that is that the input elements are created by Reagent.

pesterhazy20:06:25

But you can turn it off per component, if you just write your own React component (without using Reagent machinery)

pesterhazy20:06:22

Just using component-local state (not r/state) to keep the input field contents

pesterhazy20:06:43

The downside is that you now have two sources of truth, the this.state one (immediately updated) and the outer ratom (updates to which take a tick to propagate)

pesterhazy20:06:14

Does it make sense so far?

juhoteperi20:06:16

The render is only called from RAF so even if the state is somehow separate, it doesn't help

juhoteperi20:06:29

And render is the only way to update input value properly

pesterhazy20:06:12

Well a component updates when one of the following condition holds: (i) props changed (ii) explicitly called render and (iii) this.state changed

pesterhazy20:06:34

(iii) doesn't occur in Reagent normally, but there's no reason we couldn't use it for this purpose

pesterhazy20:06:19

IOW if you call this.setState, this will trigger a render in the same tick (as I understand it)

juhoteperi20:06:36

props change & render are the same thing, props are checked when rendering

juhoteperi20:06:11

Not sure how state would help. There still isn't a way to add this logic to all input elements.

juhoteperi20:06:45

Problem isn't the logic to immediately set input DOM value, the current solution already does that. Hard part is to get all input elements to use the fix, and this only works if the :input is created by Reagent. We can't control Input elements created directly from React.

juhoteperi20:06:30

Or did you have a idea to use this directly with custom inputs?

pesterhazy20:06:09

The idea would be to write a wrapper like this:

(defn wrapped-input-ui [props]
  (r/as-element [MaterialUI/input (assoc props :on-change (fn [] (.setState this (-> v .-target .-value))))])
  )

pesterhazy20:06:12

This is pseudo-code

pesterhazy20:06:03

It needs some scaffolding to create a proper React.Component

pesterhazy20:06:25

But ultimately it would make use of the fact that state updates are instantaneous

pesterhazy20:06:50

Reagent couldn't break that if it tried

juhoteperi20:06:45

Hmm, lets try this. Though I think need to replace :on-change and :value is often a bit hard while making sure the props work like "normal".

pesterhazy20:06:50

What's missing in the snippet is (i) passing through the value to the on-change handler from props and (ii) a way to react to changed values in "props" from the outside, by calling setState

pesterhazy20:06:04

You'd need to hook into lifecycle methods (didReceiveProps) to call setState when the value prop changes

pesterhazy20:06:41

Is it possible to get .state and to call .setState in a Reagent component?

pesterhazy20:06:47

(`r/set-state` actually just creates a ratom so that won't work)

juhoteperi20:06:49

Yes.

cljs
(defn text-field [props & _]
  (r/create-class
    {:getInitialState (fn [] #js {:value (:value props)})
     :reagent-render
     (fn [props & children]
       (this-as this
         (let [props (-> props
                         (assoc :on-change (fn [e]
                                             (.setState this #js {:value (.. e -target -value)})
                                             (if-let [f (:on-change props)]
                                               (f e)))
                                :value (.-value (.-state this)))
                         rtpl/convert-prop-value)]
           (apply r/create-element mui/TextField props (map r/as-element children)))))}))

juhoteperi20:06:02

Still broken cursor with this. Hmm.

pesterhazy20:06:31

Does it rerender?

juhoteperi20:06:22

Yes, but notice that I call the original on-change which resets the ratom.

juhoteperi20:06:44

Ah doesn't re-render without that.

pesterhazy20:06:10

How do you call the text-field comp?

juhoteperi20:06:43

Reagent :shouldComponentUpdate fn might disable state updates

pesterhazy20:06:10

If so there should be a way around that

juhoteperi20:06:49

Yes, calling forceUpdate on the component. That does enable rendering.

juhoteperi20:06:05

Hmm! Yes, this seems to work.

juhoteperi20:06:35

Nice, we found not only one fix but two today

juhoteperi20:06:58

And this works with auto height textarea because we don't have to replace the component

pesterhazy20:06:24

This one could be developed into a general wrapper that works with any component that takes "on-change" and "value" props

pesterhazy20:06:16

[wrap-it [mui/TextField {:value @state :on-change ....}]]

juhoteperi20:06:24

I'll need to implement the props change still.

pesterhazy20:06:25

or something like that

pesterhazy20:06:10

By the way, I notice that today is the deadline for Clojutre

pesterhazy20:06:29

for the CFP. I was wondering if I should submit

juhoteperi20:06:55

@pesterhazy No reason to not submit proposal if you have an idea? ๐Ÿ™‚

juhoteperi20:06:36

I might have to think if this is better fix for even basic :inputs in Reagent than current one

juhoteperi20:06:16

Less code, though it uses component-will-receive-props which is going to be deprecated

juhoteperi20:06:47

React docs even mention that setting state based on props is a bad idea ๐Ÿ˜•

pesterhazy20:06:46

Hmm maybe in the general case but in this case it seems fine

pesterhazy20:06:56

Didn't know that will-receive-props is being deprecated

pesterhazy20:06:31

Reagent's current input logic is pretty convoluted so avoiding that might be a plus

juhoteperi20:06:09

> If you used componentWillReceiveProps to โ€œresetโ€ some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead. And this is exactly what this does.

pesterhazy20:06:37

I don't understand the "uncontrolled with a key" part...

juhoteperi20:06:38

I guess it is about changing the key of uncontrolled input when the component should which to new default-value

juhoteperi20:06:08

changing key causes the component to remounted so default-value update will be rendered

juhoteperi20:06:30

This can also be implemented using normal atom, instead of react state, though that requires forceUpdate.

juhoteperi21:06:48

This could be implemented using getDerivedStateFromProps but that can't be currently used as Reagent uses legacy componentWillMount and legacy and new methods can't be mixed

pesterhazy21:06:07

@juhoteperi I guess the willReceiveProps method will continue working for some time?

pesterhazy21:06:32

Also I just submitted a CFP submission... With half an hour to spare ๐Ÿ™‚

juhoteperi21:06:05

Yes, and at some point we will need to rewrite large parts of Reagent to stop using legacy methods

juhoteperi22:06:16

@pesterhazy Okay, now I remember why this is not enough. If user does some transformation with the value, like upperCase or ignores some letters, cursor will jump because the property value is different than rendered

juhoteperi22:06:44

Cursor works for basic case where the value is not transformed

juhoteperi22:06:44

But keeping cursor position after transformations requires moving cursor back to original position: https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/template.cljs#L204-L213

juhoteperi22:06:50

Though the other fix (inputComponent) doesn't seem to work with transformation either...

juhoteperi22:06:46

Ah no, it works, I had another problem

pesterhazy22:06:30

We're basically doing the same thing as vanilla React