@@ -21,19 +21,18 @@ const TextInputState = require('./TextInputState');
21
21
const TouchableWithoutFeedback = require ( '../Touchable/TouchableWithoutFeedback' ) ;
22
22
23
23
const invariant = require ( 'invariant' ) ;
24
+ const nullthrows = require ( 'nullthrows' ) ;
24
25
const requireNativeComponent = require ( '../../ReactNative/requireNativeComponent' ) ;
26
+ const setAndForwardRef = require ( '../../Utilities/setAndForwardRef' ) ;
25
27
26
28
import type { TextStyleProp , ViewStyleProp } from '../../StyleSheet/StyleSheet' ;
27
29
import type { ColorValue } from '../../StyleSheet/StyleSheetTypes' ;
28
30
import type { ViewProps } from '../View/ViewPropTypes' ;
29
31
import type { SyntheticEvent , ScrollEvent } from '../../Types/CoreEventTypes' ;
30
32
import type { PressEvent } from '../../Types/CoreEventTypes' ;
31
- import type {
32
- HostComponent ,
33
- MeasureOnSuccessCallback ,
34
- MeasureInWindowOnSuccessCallback ,
35
- MeasureLayoutOnSuccessCallback ,
36
- } from '../../Renderer/shims/ReactNativeTypes' ;
33
+ import type { HostComponent } from '../../Renderer/shims/ReactNativeTypes' ;
34
+
35
+ type ReactRefSetter < T > = { current : null | T } | ( ( ref : null | T ) => mixed ) ;
37
36
38
37
let AndroidTextInput ;
39
38
let RCTMultilineTextInputView ;
@@ -676,6 +675,10 @@ export type Props = $ReadOnly<{|
676
675
* If `true`, contextMenuHidden is hidden. The default value is `false`.
677
676
*/
678
677
contextMenuHidden ?: ?boolean ,
678
+
679
+ forwardedRef ?: ?ReactRefSetter <
680
+ React . ElementRef < HostComponent < mixed >> & ImperativeMethods ,
681
+ > ,
679
682
| } > ;
680
683
681
684
type DefaultProps = $ReadOnly < { |
@@ -684,11 +687,11 @@ type DefaultProps = $ReadOnly<{|
684
687
underlineColorAndroid : 'transparent' ,
685
688
| } > ;
686
689
687
- type State = { |
688
- currentlyFocusedField : typeof TextInputState . currentlyFocusedField ,
689
- focusTextInput : typeof TextInputState . focusTextInput ,
690
- blurTextInput : typeof TextInputState . blurTextInput ,
691
- | } ;
690
+ type ImperativeMethods = $ReadOnly < { |
691
+ clear : ( ) => void ,
692
+ isFocused : ( ) => boolean ,
693
+ getNativeRef : ( ) => ? React . ElementRef < HostComponent < mixed >> ,
694
+ | } > ;
692
695
693
696
const emptyFunctionThatReturnsTrue = ( ) => true ;
694
697
@@ -803,22 +806,14 @@ const emptyFunctionThatReturnsTrue = () => true;
803
806
* or control this param programmatically with native code.
804
807
*
805
808
*/
806
- class TextInput extends React . Component < Props , State > {
809
+ class InternalTextInput extends React . Component < Props > {
807
810
static defaultProps : DefaultProps = {
808
811
allowFontScaling : true ,
809
812
rejectResponderTermination : true ,
810
813
underlineColorAndroid : 'transparent' ,
811
814
} ;
812
815
813
- static propTypes = DeprecatedTextInputPropTypes ;
814
-
815
- static State : State = {
816
- currentlyFocusedField : TextInputState . currentlyFocusedField ,
817
- focusTextInput : TextInputState . focusTextInput ,
818
- blurTextInput : TextInputState . blurTextInput ,
819
- } ;
820
-
821
- _inputRef : ?React . ElementRef < HostComponent < mixed >> = null ;
816
+ _inputRef : null | React . ElementRef < HostComponent < mixed >> = null ;
822
817
_focusSubscription : ?Function = undefined ;
823
818
_lastNativeText : ?Stringish = null ;
824
819
_lastNativeSelection : ?Selection = null ;
@@ -833,7 +828,11 @@ class TextInput extends React.Component<Props, State> {
833
828
}
834
829
835
830
if ( this . props . autoFocus ) {
836
- this . _rafId = requestAnimationFrame ( this . focus ) ;
831
+ this . _rafId = requestAnimationFrame ( ( ) => {
832
+ if ( this . _inputRef ) {
833
+ this . _inputRef . focus ( ) ;
834
+ }
835
+ } ) ;
837
836
}
838
837
}
839
838
@@ -874,7 +873,7 @@ class TextInput extends React.Component<Props, State> {
874
873
componentWillUnmount ( ) {
875
874
this . _focusSubscription && this . _focusSubscription . remove ( ) ;
876
875
if ( this . isFocused ( ) ) {
877
- this . blur ( ) ;
876
+ nullthrows ( this . _inputRef ) . blur ( ) ;
878
877
}
879
878
const tag = ReactNative . findNodeHandle ( this . _inputRef ) ;
880
879
if ( tag != null ) {
@@ -889,7 +888,9 @@ class TextInput extends React.Component<Props, State> {
889
888
* Removes all text from the `TextInput`.
890
889
*/
891
890
clear : ( ) => void = ( ) => {
892
- this . setNativeProps ( { text : '' } ) ;
891
+ if ( this . _inputRef != null ) {
892
+ this . _inputRef . setNativeProps ( { text : '' } ) ;
893
+ }
893
894
} ;
894
895
895
896
/**
@@ -906,35 +907,6 @@ class TextInput extends React.Component<Props, State> {
906
907
return this . _inputRef ;
907
908
} ;
908
909
909
- // From NativeMethodsMixin
910
- // We need these instead of using forwardRef because we also have the other
911
- // methods we expose
912
- blur : ( ) => void = ( ) => {
913
- this . _inputRef && this . _inputRef . blur ( ) ;
914
- } ;
915
- focus : ( ) => void = ( ) => {
916
- this . _inputRef && this . _inputRef . focus ( ) ;
917
- } ;
918
- measure : ( callback : MeasureOnSuccessCallback ) => void = callback => {
919
- this . _inputRef && this . _inputRef . measure ( callback ) ;
920
- } ;
921
- measureInWindow : (
922
- callback : MeasureInWindowOnSuccessCallback ,
923
- ) => void = callback => {
924
- this . _inputRef && this . _inputRef . measureInWindow ( callback ) ;
925
- } ;
926
- measureLayout : (
927
- relativeToNativeNode : number | React . ElementRef < HostComponent < mixed >> ,
928
- onSuccess : MeasureLayoutOnSuccessCallback ,
929
- onFail ?: ( ) => void ,
930
- ) => void = ( relativeToNativeNode , onSuccess , onFail ) => {
931
- this . _inputRef &&
932
- this . _inputRef . measureLayout ( relativeToNativeNode , onSuccess , onFail ) ;
933
- } ;
934
- setNativeProps : ( nativeProps : Object ) => void = nativeProps => {
935
- this . _inputRef && this . _inputRef . setNativeProps ( nativeProps ) ;
936
- } ;
937
-
938
910
render ( ) : React . Node {
939
911
let textInput = null ;
940
912
let additionalTouchableProps : { |
@@ -1045,13 +1017,44 @@ class TextInput extends React.Component<Props, State> {
1045
1017
: '' ;
1046
1018
}
1047
1019
1048
- _setNativeRef = ( ref : any ) => {
1049
- this . _inputRef = ref ;
1050
- } ;
1020
+ _setNativeRef = setAndForwardRef ( {
1021
+ getForwardedRef : ( ) => this . props . forwardedRef ,
1022
+ setLocalRef : ref => {
1023
+ this . _inputRef = ref ;
1024
+
1025
+ /*
1026
+ Hi reader from the future. I'm sorry for this.
1027
+
1028
+ This is a hack. Ideally we would forwardRef to the underlying
1029
+ host component. However, since TextInput has it's own methods that can be
1030
+ called as well, if we used the standard forwardRef then these
1031
+ methods wouldn't be accessible and thus be a breaking change.
1032
+
1033
+ We have a couple of options of how to handle this:
1034
+ - Return a new ref with everything we methods from both. This is problematic
1035
+ because we need React to also know it is a host component which requires
1036
+ internals of the class implementation of the ref.
1037
+ - Break the API and have some other way to call one set of the methods or
1038
+ the other. This is our long term approach as we want to eventually
1039
+ get the methods on host components off the ref. So instead of calling
1040
+ ref.measure() you might call ReactNative.measure(ref). This would hopefully
1041
+ let the ref for TextInput then have the methods like `.clear`. Or we do it
1042
+ the other way and make it TextInput.clear(textInputRef) which would be fine
1043
+ too. Either way though is a breaking change that is longer term.
1044
+ - Mutate this ref. :( Gross, but accomplishes what we need in the meantime
1045
+ before we can get to the long term breaking change.
1046
+ */
1047
+ if ( ref ) {
1048
+ ref . clear = this . clear ;
1049
+ ref . isFocused = this . isFocused ;
1050
+ ref . getNativeRef = this . getNativeRef ;
1051
+ }
1052
+ } ,
1053
+ } ) ;
1051
1054
1052
1055
_onPress = ( event : PressEvent ) => {
1053
1056
if ( this . props . editable || this . props . editable === undefined ) {
1054
- this . focus ( ) ;
1057
+ nullthrows ( this . _inputRef ) . focus ( ) ;
1055
1058
}
1056
1059
} ;
1057
1060
@@ -1117,16 +1120,44 @@ class TextInput extends React.Component<Props, State> {
1117
1120
} ;
1118
1121
}
1119
1122
1120
- class InternalTextInputType extends ReactNative . NativeComponent < Props > {
1121
- clear ( ) { }
1122
-
1123
- getNativeRef ( ) : ?React .ElementRef < HostComponent < mixed >> { }
1124
-
1125
- // $FlowFixMe
1126
- isFocused ( ) : boolean { }
1127
- }
1123
+ const ExportedForwardRef : React . AbstractComponent <
1124
+ React . ElementConfig < typeof InternalTextInput > ,
1125
+ React . ElementRef < HostComponent < mixed >> & ImperativeMethods ,
1126
+ > = React . forwardRef ( function TextInput (
1127
+ props ,
1128
+ forwardedRef : ReactRefSetter <
1129
+ React . ElementRef < HostComponent < mixed >> & ImperativeMethods ,
1130
+ > ,
1131
+ ) {
1132
+ return < InternalTextInput { ...props } forwardedRef = { forwardedRef } /> ;
1133
+ } ) ;
1128
1134
1129
- const TypedTextInput = ( ( TextInput : any ) : Class < InternalTextInputType > ) ;
1135
+ // $FlowFixMe
1136
+ ExportedForwardRef . defaultProps = {
1137
+ allowFontScaling : true ,
1138
+ rejectResponderTermination : true ,
1139
+ underlineColorAndroid : 'transparent' ,
1140
+ } ;
1141
+
1142
+ // TODO: Deprecate this
1143
+ // $FlowFixMe
1144
+ ExportedForwardRef . propTypes = DeprecatedTextInputPropTypes ;
1145
+
1146
+ // $FlowFixMe
1147
+ ExportedForwardRef . State = {
1148
+ currentlyFocusedField : TextInputState . currentlyFocusedField ,
1149
+ focusTextInput : TextInputState . focusTextInput ,
1150
+ blurTextInput : TextInputState . blurTextInput ,
1151
+ } ;
1152
+
1153
+ type TextInputComponentStatics = $ReadOnly < { |
1154
+ State : $ReadOnly < { |
1155
+ currentlyFocusedField : typeof TextInputState . currentlyFocusedField ,
1156
+ focusTextInput : typeof TextInputState . focusTextInput ,
1157
+ blurTextInput : typeof TextInputState . blurTextInput ,
1158
+ | } > ,
1159
+ propTypes : typeof DeprecatedTextInputPropTypes ,
1160
+ | } > ;
1130
1161
1131
1162
const styles = StyleSheet . create ( {
1132
1163
multilineInput : {
@@ -1137,4 +1168,11 @@ const styles = StyleSheet.create({
1137
1168
} ,
1138
1169
} ) ;
1139
1170
1140
- module . exports = TypedTextInput ;
1171
+ module . exports = ( ( ExportedForwardRef : any ) : React . AbstractComponent <
1172
+ React . ElementConfig < typeof InternalTextInput > ,
1173
+ $ReadOnly < { |
1174
+ ...React . ElementRef < HostComponent < mixed >> ,
1175
+ ...ImperativeMethods ,
1176
+ | } > ,
1177
+ > &
1178
+ TextInputComponentStatics ) ;
0 commit comments