This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-06-17
Channels
- # aws-lambda (1)
- # beginners (55)
- # cider (19)
- # clojure (96)
- # clojure-spec (2)
- # clojure-uk (6)
- # clojurescript (22)
- # datomic (6)
- # editors (13)
- # emacs (6)
- # euroclojure (1)
- # hoplon (3)
- # immutant (1)
- # jobs-rus (2)
- # off-topic (2)
- # onyx (4)
- # portkey (3)
- # re-frame (76)
- # reagent (110)
- # shadow-cljs (13)
- # spacemacs (10)
- # specter (9)
- # tools-deps (9)
- # vim (7)
@juhoteperi using
<!-- Polyfills -->
<!--[if lt IE 10]>
<script src=""></script>
<![endif]-->
<script src=""></script>
solve the problemExamples 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
https://github.com/reagent-project/reagent/blob/master/doc/examples/material-ui.md
@juhoteperi thanks for putting up the example, that's very useful
So your solution is to replace the <input>
in material-ui with a custom component, correct?
Did you experiment with other approaches that do not rely on the 3rd party supplying a hook to swap in a custom <input>
renderer?
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.
I'm not aware of other fixes that would work properly.
Me either
Unfortunately this won't necessarily work with every other 3rd party styled input component
@valtteri sounds like "reagent+input" is on collective our minds ๐
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.
Or well, not wasted since I came up with something that @juhoteperi could polish into a nice example + docs. ๐
I hadn't thought of this option
@valtteri what did you find?
This fix. I just copied the idea and wrote docs ๐
It's a vexing problem. I think there must be a more general solution out there, but I struggle to find it
Ah, good job @valtteri ๐
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.
Agree that passing components is very common
I was already quite desperate with hacky uncontrolled inputs and other not-so-good alternatives.
I'm not sure it would work with the ReactNative input components for example
@valtteri what would you say is the core of the problem?
I guess the answer must include (a) Ratoms and (b) asynchronous rendering
To prevent problems, the on-change event needs to update the "value" prop in the same event loop tick
But that's out of the question if a Ratom is used
(That's my understanding of the problem)
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.
So one solution could be a wrapper component that updates the value immediately and somehow also communicates with a Ratom
The wrapper component could be implemented by calling r/create-class directly
I think the problem could be reproduced in Reagent without Ratoms, with just React state.
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.
So my idea was to "decouple" an input in a wrapper comp that avoids requestAnimationFrame
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.
But you can turn it off per component, if you just write your own React component (without using Reagent machinery)
Just using component-local state (not r/state) to keep the input field contents
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)
Does it make sense so far?
The render is only called from RAF so even if the state is somehow separate, it doesn't help
And render is the only way to update input value properly
Well a component updates when one of the following condition holds: (i) props changed (ii) explicitly called render and (iii) this.state changed
(iii) doesn't occur in Reagent normally, but there's no reason we couldn't use it for this purpose
IOW if you call this.setState, this will trigger a render in the same tick (as I understand it)
props change & render are the same thing, props are checked when rendering
Not sure how state would help. There still isn't a way to add this logic to all input elements.
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.
Or did you have a idea to use this directly with custom inputs?
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))))])
)
This is pseudo-code
It needs some scaffolding to create a proper React.Component
But ultimately it would make use of the fact that state updates are instantaneous
Reagent couldn't break that if it tried
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".
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
You'd need to hook into lifecycle methods (didReceiveProps) to call setState when the value
prop changes
Is it possible to get .state
and to call .setState
in a Reagent component?
(`r/set-state` actually just creates a ratom so that won't work)
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)))))}))
Still broken cursor with this. Hmm.
Does it rerender?
Yes, but notice that I call the original on-change which resets the ratom.
Ah doesn't re-render without that.
How do you call the text-field comp?
Reagent :shouldComponentUpdate fn might disable state updates
If so there should be a way around that
Yes, calling forceUpdate
on the component. That does enable rendering.
Hmm! Yes, this seems to work.
Fantastic
Nice, we found not only one fix but two today
And this works with auto height textarea because we don't have to replace the component
This one could be developed into a general wrapper that works with any component that takes "on-change" and "value" props
[wrap-it [mui/TextField {:value @state :on-change ....}]]
I'll need to implement the props change still.
or something like that
By the way, I notice that today is the deadline for Clojutre
for the CFP. I was wondering if I should submit
@pesterhazy No reason to not submit proposal if you have an idea? ๐
I might have to think if this is better fix for even basic :input
s in Reagent than current one
Less code, though it uses component-will-receive-props
which is going to be deprecated
React docs even mention that setting state based on props is a bad idea ๐
Hmm maybe in the general case but in this case it seems fine
Didn't know that will-receive-props is being deprecated
Reagent's current input logic is pretty convoluted so avoiding that might be a plus
> 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.
I don't understand the "uncontrolled with a key" part...
I guess it is about changing the key of uncontrolled input when the component should which to new default-value
changing key causes the component to remounted so default-value update will be rendered
This can also be implemented using normal atom, instead of react state, though that requires forceUpdate
.
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
@juhoteperi I guess the willReceiveProps method will continue working for some time?
Also I just submitted a CFP submission... With half an hour to spare ๐
Yes, and at some point we will need to rewrite large parts of Reagent to stop using legacy methods
@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
Cursor works for basic case where the value is not transformed
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
Though the other fix (inputComponent) doesn't seem to work with transformation either...
Ah no, it works, I had another problem
We're basically doing the same thing as vanilla React