-
Notifications
You must be signed in to change notification settings - Fork 24.7k
Implement TextInput onContentSizeChange #8457
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
Implement TextInput onContentSizeChange #8457
Conversation
By analyzing the blame information on this pull request, we identified @brunobar79 and @Bhullnatik to be potential reviewers. |
@@ -311,6 +311,11 @@ const TextInput = React.createClass({ | |||
*/ | |||
onChangeText: PropTypes.func, | |||
/** | |||
* Callback this is called when the text input's content size changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callback this
=> Callback that
Copying @andreicoman11, answer from internal thread: From both discussions, it looks like the best way to solve this is to add an "autoScale" prop, so that the multiline textinput grows to fit the text inside. Nick seems to have tried this in the past for iOS, I also gave it a shot on Android and it looked doable, but haven't had time to see it through. |
|
||
// Start sending content size updates only after the view has been layed out | ||
// otherwise we send multiple events with bad dimentions on initial render. | ||
_sendContentSizeUpdates = YES; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ping @dmmiller, what do you think from product parity POV? |
I don't think this is actually what we want for all cases -- we can build That said, I think it could be valuable for edit: not worded the best above, to clarify: we probably want both |
This looks great to me @janicduplessis -- thanks! |
One advantage of implementing autoScale in native is that it would probably be easier to prevent any text flickering when the input is growing or shrinking, right now with this implementation sometimes on iOS the text moves up for one frame when adding a new line and on android it does move a bit when removing a line. Not sure if there is a way to improve this as everything is async. The main advantage is as @brentvatne mentioned is that it is a lot more flexible and allows implementing multiple behaviours. |
Also this is only implemented for RCTTextView and not RCTTextField as I didn't see the need for it with a TextField but we might want to implement it for consistency sake. |
onContentSizeChange={(contentSize) => { | ||
this.setState({height: contentSize.height}); | ||
}} | ||
onChangeText={(text) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe leave this until we've implemented onTextChange
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onChangeText
already works, I just mentioned that we should probably rename it for consistency. Unless you mean something else?
@@ -102,7 +103,23 @@ public ReactEditText(Context context) { | |||
// TODO: t6408636 verify if we should schedule a layout after a View does a requestLayout() | |||
@Override | |||
public boolean isLayoutRequested() { | |||
return false; | |||
// If we are watching and updating container height based on content size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you return true
here, in the case of single-line text, the cursor will go out of view after you've typed after the end of the visible line. That means that onContentSizeChange
should be limited to only work with multiline texts.
Let me expand on what I've posted internally. The problem with implementing behaviors related to size change on top of callbacks is that they will flicker often, because of the asynchronicity of it all. If JS is busy at any point and cannot react to a size change immediately, there will always be a lag between content size change and implemented JS behavior. The worse the device, the more often you will get this lag (looking at you android). The more complicated the JS behavior, the easier it will be for this to happen. |
@@ -85,9 +85,6 @@ public void scrollTo( | |||
Map getExportedCustomDirectEventTypeConstants() { | |||
return MapBuilder.builder() | |||
.put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll")) | |||
.put( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't get rid of this. We use this internally in apps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The event was moved here https://github.com/facebook/react-native/pull/8457/files#diff-0404c7801b7b68d8a58a11f338c5d666R74 since onContentSizeChange is now used by scollview and textinput. Is there a difference between doing it in the manager vs in uimanager that I'm not aware of?
@nicklockwood or @javache Have either of you had a chance to look at the obj-c implementation? |
@@ -673,6 +690,12 @@ const TextInput = React.createClass({ | |||
} | |||
}, | |||
|
|||
_onContentSizeChange(event: Event) { | |||
if (this.props.onContentSizeChange) { | |||
this.props.onContentSizeChange(event.nativeEvent.contentSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems unlike any of the other event handlers we provide. If we add a new property to this event, it's really hard to make it backwards compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I wasn't sure about this, I did like onChangeText
where we pass only the text. Personally I prefer having only the relevant value passed the the callback instead of the whole event in case where the event is pretty specific.
With that said I don't mind changing this to passing the event either as I can see it has some advantages and we may use this pattern more often.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With destructuring assignment the syntax overhead of unpacking an object vs. getting just the relevant value is only { }
so I think we should go with the more future-proof approach and use the object.
Objective-C looks ok, but I agree with @andreicoman11's concerns about the flicker you'll see when JS is running behind. |
@@ -76,18 +76,21 @@ var TextEventsExample = React.createClass({ | |||
class AutoExpandingTextInput extends React.Component { | |||
constructor(props) { | |||
super(props); | |||
this.state = {text: '', height: 0}; | |||
this.state = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should FlowType State type be added for this?
@andreicoman11 - given that @javache gave a 👍 on the ObjC side, is this ok to ship? |
Lgtm. @dmmiller do you have any other concerns about this? Otherwise we can shipit |
Let's go for it. @facebook-github-bot shipit |
Thanks for importing. If you are an FB employee go to Phabricator to review. |
Summary: This adds proper support for tracking a TextInput content size as discussed in #6552 by adding a new callback that is called every time the content size changes including when first rendering the view. Some points that are up for discussion are what do we want to do with the onChange callback as I don't see any use left for it now that we can track text change in onChangeText and size changes in onContentSizeChange. Also a bit off topic but should we consider renaming onChangeText to onTextChange to keep the naming more consistent (see [this naming justification](https://twitter.com/notbrent/status/709445076850597888)). This is split in 2 commits for easier review, one for iOS and one for android. The iOS implementation simply checks if the content size has changed everytime we update it and fire the callback, the only small issue was that the content size had several different values on initial render so I added a check to not fire events before the layoutSubviews where at this point the value is g Closes facebook/react-native#8457 Differential Revision: D3528202 Pulled By: dmmiller fbshipit-source-id: fefe83f10cc5bfde1f5937c48c88b10408e58d9d
@janicduplessis have you tested this on android? Because I happened to play around with it today and it was not working. More precisely, setting 'height' on the component will cause no 'onLayout' events to be dispatched, which was the whole point of this commit. |
I tried to merge this pull request into the Facebook internal repo but some checks failed. To unblock yourself please check the following: Does this pull request pass all open source tests on GitHub? If not please fix those. Does the code still apply cleanly on top of GitHub master? If not can please rebase. In all other cases this means some internal test failed, for example a part of a fb app won't work with this pull request. I've added the Import Failed label to this pull request so it is easy for someone at fb to find the pull request and check what failed. If you don't see anyone comment in a few days feel free to comment mentioning one of the core contributors to the project so they get a notification. |
@andreicoman11 I tested using this example in UIExplorer on android, let me know if that works for you. https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/js/TextInputExample.android.js#L81 |
@jeanregisser that is why I reopened this, the example is not working. The way it was failing indicated to me that this might've never worked properly. But if you're certain this worked when this commit landed, then we can look into what has changed and close this. |
Turns out this is only broken on some emulators, unclear why. Will investigate independent of this. |
@andreicoman11 That's weird, thanks for looking into it! |
Summary: This adds proper support for tracking a TextInput content size as discussed in facebook#6552 by adding a new callback that is called every time the content size changes including when first rendering the view. Some points that are up for discussion are what do we want to do with the onChange callback as I don't see any use left for it now that we can track text change in onChangeText and size changes in onContentSizeChange. Also a bit off topic but should we consider renaming onChangeText to onTextChange to keep the naming more consistent (see [this naming justification](https://twitter.com/notbrent/status/709445076850597888)). This is split in 2 commits for easier review, one for iOS and one for android. The iOS implementation simply checks if the content size has changed everytime we update it and fire the callback, the only small issue was that the content size had several different values on initial render so I added a check to not fire events before the layoutSubviews where at this point the value is g Closes facebook#8457 Differential Revision: D3528202 Pulled By: dmmiller fbshipit-source-id: fefe83f10cc5bfde1f5937c48c88b10408e58d9d
Well I also have the problem on a Samsung S4 Mini. |
Summary: This adds proper support for tracking a TextInput content size as discussed in facebook#6552 by adding a new callback that is called every time the content size changes including when first rendering the view. Some points that are up for discussion are what do we want to do with the onChange callback as I don't see any use left for it now that we can track text change in onChangeText and size changes in onContentSizeChange. Also a bit off topic but should we consider renaming onChangeText to onTextChange to keep the naming more consistent (see [this naming justification](https://twitter.com/notbrent/status/709445076850597888)). This is split in 2 commits for easier review, one for iOS and one for android. The iOS implementation simply checks if the content size has changed everytime we update it and fire the callback, the only small issue was that the content size had several different values on initial render so I added a check to not fire events before the layoutSubviews where at this point the value is g Closes facebook#8457 Differential Revision: D3528202 Pulled By: dmmiller fbshipit-source-id: fefe83f10cc5bfde1f5937c48c88b10408e58d9d
Summary: This adds proper support for tracking a TextInput content size as discussed in #6552 by adding a new callback that is called every time the content size changes including when first rendering the view. Some points that are up for discussion are what do we want to do with the onChange callback as I don't see any use left for it now that we can track text change in onChangeText and size changes in onContentSizeChange. Also a bit off topic but should we consider renaming onChangeText to onTextChange to keep the naming more consistent (see [this naming justification](https://twitter.com/notbrent/status/709445076850597888)). This is split in 2 commits for easier review, one for iOS and one for android. The iOS implementation simply checks if the content size has changed everytime we update it and fire the callback, the only small issue was that the content size had several different values on initial render so I added a check to not fire events before the layoutSubviews where at this point the value is g Closes facebook/react-native#8457 Differential Revision: D3528202 Pulled By: dmmiller fbshipit-source-id: fefe83f10cc5bfde1f5937c48c88b10408e58d9d
This adds proper support for tracking a TextInput content size as discussed in #6552 by adding a new callback that is called every time the content size changes including when first rendering the view.
Some points that are up for discussion are what do we want to do with the onChange callback as I don't see any use left for it now that we can track text change in onChangeText and size changes in onContentSizeChange. Also a bit off topic but should we consider renaming onChangeText to onTextChange to keep the naming more consistent (see this naming justification).
This is split in 2 commits for easier review, one for iOS and one for android.
The iOS implementation simply checks if the content size has changed everytime we update it and fire the callback, the only small issue was that the content size had several different values on initial render so I added a check to not fire events before the layoutSubviews where at this point the value is good.
The Android implementation is based on @brentvatne work here that has been used for a while in exponent.
* Test plan*
Tested that the event was properly fired only once per content size change with the right value.
Tested that the event is called initially when there is already some text in the input.
Tested that the event is called when resizing the text view frame.