Skip to content

Commit 6286270

Browse files
elicwhitefacebook-github-bot
authored andcommitted
Migrate TextInputState to take a ref instead of a reactTag
Summary: Changelog: [General][Breaking] Multiple deprecations and breaking changes to TextInputState. Use native component refs instead of react tags Reviewed By: JoshuaGross Differential Revision: D19458214 fbshipit-source-id: f67649657fa44b6c707a0ac91f07d2ceb433bc70
1 parent 862c719 commit 6286270

File tree

5 files changed

+163
-45
lines changed

5 files changed

+163
-45
lines changed

Libraries/Components/ScrollResponder.js

+28-9
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@ const ScrollResponderMixin = {
183183
return false;
184184
}
185185

186-
const currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
186+
const currentlyFocusedInput = TextInputState.currentlyFocusedInput();
187187

188188
if (
189189
this.props.keyboardShouldPersistTaps === 'handled' &&
190-
currentlyFocusedTextInput != null &&
191-
ReactNative.findNodeHandle(e.target) !== currentlyFocusedTextInput
190+
currentlyFocusedInput != null &&
191+
e.target !== currentlyFocusedInput
192192
) {
193193
return true;
194194
}
@@ -224,17 +224,26 @@ const ScrollResponderMixin = {
224224
// and a new touch starts with a non-textinput target (in which case the
225225
// first tap should be sent to the scroll view and dismiss the keyboard,
226226
// then the second tap goes to the actual interior view)
227-
const currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
227+
const currentlyFocusedTextInput = TextInputState.currentlyFocusedInput();
228228
const {keyboardShouldPersistTaps} = this.props;
229229
const keyboardNeverPersistTaps =
230230
!keyboardShouldPersistTaps || keyboardShouldPersistTaps === 'never';
231231

232-
const reactTag = ReactNative.findNodeHandle(e.target);
232+
if (typeof e.target === 'number') {
233+
if (__DEV__) {
234+
console.error(
235+
'Did not expect event target to be a number. Should have been a native component',
236+
);
237+
}
238+
239+
return false;
240+
}
241+
233242
if (
234243
keyboardNeverPersistTaps &&
235244
currentlyFocusedTextInput != null &&
236-
reactTag &&
237-
!TextInputState.isTextInput(reactTag)
245+
e.target != null &&
246+
!TextInputState.isTextInput(e.target)
238247
) {
239248
return true;
240249
}
@@ -300,14 +309,24 @@ const ScrollResponderMixin = {
300309
scrollResponderHandleResponderRelease: function(e: PressEvent) {
301310
this.props.onResponderRelease && this.props.onResponderRelease(e);
302311

312+
if (typeof e.target === 'number') {
313+
if (__DEV__) {
314+
console.error(
315+
'Did not expect event target to be a number. Should have been a native component',
316+
);
317+
}
318+
319+
return;
320+
}
321+
303322
// By default scroll views will unfocus a textField
304323
// if another touch occurs outside of it
305-
const currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
324+
const currentlyFocusedTextInput = TextInputState.currentlyFocusedInput();
306325
if (
307326
this.props.keyboardShouldPersistTaps !== true &&
308327
this.props.keyboardShouldPersistTaps !== 'always' &&
309328
currentlyFocusedTextInput != null &&
310-
ReactNative.findNodeHandle(e.target) !== currentlyFocusedTextInput &&
329+
e.target !== currentlyFocusedTextInput &&
311330
!this.state.observedScrollSinceBecomingResponder &&
312331
!this.state.becameResponderWhileAnimating
313332
) {

Libraries/Components/TextInput/TextInput.js

+11-10
Original file line numberDiff line numberDiff line change
@@ -888,12 +888,13 @@ function InternalTextInput(props: Props): React.Node {
888888
useFocusOnMount(props.autoFocus, inputRef);
889889

890890
useEffect(() => {
891-
const tag = ReactNative.findNodeHandle(inputRef.current);
892-
if (tag != null) {
893-
TextInputState.registerInput(tag);
891+
const inputRefValue = inputRef.current;
892+
893+
if (inputRefValue != null) {
894+
TextInputState.registerInput(inputRefValue);
894895

895896
return () => {
896-
TextInputState.unregisterInput(tag);
897+
TextInputState.unregisterInput(inputRefValue);
897898
};
898899
}
899900
}, [inputRef]);
@@ -915,10 +916,7 @@ function InternalTextInput(props: Props): React.Node {
915916

916917
// TODO: Fix this returning true on null === null, when no input is focused
917918
function isFocused(): boolean {
918-
return (
919-
TextInputState.currentlyFocusedField() ===
920-
ReactNative.findNodeHandle(inputRef.current)
921-
);
919+
return TextInputState.currentlyFocusedInput() === inputRef.current;
922920
}
923921

924922
function getNativeRef(): ?React.ElementRef<HostComponent<mixed>> {
@@ -1009,14 +1007,14 @@ function InternalTextInput(props: Props): React.Node {
10091007
};
10101008

10111009
const _onFocus = (event: FocusEvent) => {
1012-
TextInputState.focusField(ReactNative.findNodeHandle(inputRef.current));
1010+
TextInputState.focusInput(inputRef.current);
10131011
if (props.onFocus) {
10141012
props.onFocus(event);
10151013
}
10161014
};
10171015

10181016
const _onBlur = (event: BlurEvent) => {
1019-
TextInputState.blurField(ReactNative.findNodeHandle(inputRef.current));
1017+
TextInputState.blurInput(inputRef.current);
10201018
if (props.onBlur) {
10211019
props.onBlur(event);
10221020
}
@@ -1143,13 +1141,16 @@ ExportedForwardRef.propTypes = DeprecatedTextInputPropTypes;
11431141

11441142
// $FlowFixMe
11451143
ExportedForwardRef.State = {
1144+
currentlyFocusedInput: TextInputState.currentlyFocusedInput,
1145+
11461146
currentlyFocusedField: TextInputState.currentlyFocusedField,
11471147
focusTextInput: TextInputState.focusTextInput,
11481148
blurTextInput: TextInputState.blurTextInput,
11491149
};
11501150

11511151
type TextInputComponentStatics = $ReadOnly<{|
11521152
State: $ReadOnly<{|
1153+
currentlyFocusedInput: typeof TextInputState.currentlyFocusedInput,
11531154
currentlyFocusedField: typeof TextInputState.currentlyFocusedField,
11541155
focusTextInput: typeof TextInputState.focusTextInput,
11551156
blurTextInput: typeof TextInputState.blurTextInput,

Libraries/Components/TextInput/TextInputState.js

+104-18
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,82 @@
1414

1515
'use strict';
1616

17+
const React = require('react');
1718
const Platform = require('../../Utilities/Platform');
1819
const UIManager = require('../../ReactNative/UIManager');
20+
const {findNodeHandle} = require('../../Renderer/shims/ReactNative');
1921

20-
let currentlyFocusedID: ?number = null;
22+
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
23+
type ComponentRef = React.ElementRef<HostComponent<mixed>>;
24+
25+
let currentlyFocusedInputRef: ?ComponentRef = null;
2126
const inputs = new Set();
2227

28+
function currentlyFocusedInput(): ?ComponentRef {
29+
return currentlyFocusedInputRef;
30+
}
31+
2332
/**
2433
* Returns the ID of the currently focused text field, if one exists
2534
* If no text field is focused it returns null
2635
*/
2736
function currentlyFocusedField(): ?number {
28-
return currentlyFocusedID;
37+
if (__DEV__) {
38+
console.error(
39+
'currentlyFocusedField is deprecated and will be removed in a future release. Use currentlyFocusedInput',
40+
);
41+
}
42+
43+
return findNodeHandle(currentlyFocusedInputRef);
44+
}
45+
46+
function focusInput(textField: ?ComponentRef): void {
47+
if (currentlyFocusedInputRef !== textField && textField != null) {
48+
currentlyFocusedInputRef = textField;
49+
}
50+
}
51+
52+
function blurInput(textField: ?ComponentRef): void {
53+
if (currentlyFocusedInputRef === textField && textField != null) {
54+
currentlyFocusedInputRef = null;
55+
}
2956
}
3057

3158
function focusField(textFieldID: ?number): void {
32-
if (currentlyFocusedID !== textFieldID && textFieldID != null) {
33-
currentlyFocusedID = textFieldID;
59+
if (__DEV__) {
60+
console.error('focusField no longer works. Use focusInput');
3461
}
62+
63+
return;
3564
}
3665

3766
function blurField(textFieldID: ?number) {
38-
if (currentlyFocusedID === textFieldID && textFieldID != null) {
39-
currentlyFocusedID = null;
67+
if (__DEV__) {
68+
console.error('blurField no longer works. Use blurInput');
4069
}
70+
71+
return;
4172
}
4273

4374
/**
4475
* @param {number} TextInputID id of the text field to focus
4576
* Focuses the specified text field
4677
* noop if the text field was already focused
4778
*/
48-
function focusTextInput(textFieldID: ?number) {
49-
if (currentlyFocusedID !== textFieldID && textFieldID != null) {
50-
focusField(textFieldID);
79+
function focusTextInput(textField: ?ComponentRef) {
80+
if (typeof textField === 'number') {
81+
if (__DEV__) {
82+
console.error(
83+
'focusTextInput must be called with a host component. Passing a react tag is deprecated.',
84+
);
85+
}
86+
87+
return;
88+
}
89+
90+
if (currentlyFocusedInputRef !== textField && textField != null) {
91+
const textFieldID = findNodeHandle(textField);
92+
focusInput(textField);
5193
if (Platform.OS === 'ios') {
5294
UIManager.focus(textFieldID);
5395
} else if (Platform.OS === 'android') {
@@ -66,9 +108,20 @@ function focusTextInput(textFieldID: ?number) {
66108
* Unfocuses the specified text field
67109
* noop if it wasn't focused
68110
*/
69-
function blurTextInput(textFieldID: ?number) {
70-
if (currentlyFocusedID === textFieldID && textFieldID != null) {
71-
blurField(textFieldID);
111+
function blurTextInput(textField: ?ComponentRef) {
112+
if (typeof textField === 'number') {
113+
if (__DEV__) {
114+
console.error(
115+
'focusTextInput must be called with a host component. Passing a react tag is deprecated.',
116+
);
117+
}
118+
119+
return;
120+
}
121+
122+
if (currentlyFocusedInputRef === textField && textField != null) {
123+
const textFieldID = findNodeHandle(textField);
124+
blurInput(textField);
72125
if (Platform.OS === 'ios') {
73126
UIManager.blur(textFieldID);
74127
} else if (Platform.OS === 'android') {
@@ -82,19 +135,52 @@ function blurTextInput(textFieldID: ?number) {
82135
}
83136
}
84137

85-
function registerInput(textFieldID: number) {
86-
inputs.add(textFieldID);
138+
function registerInput(textField: ComponentRef) {
139+
if (typeof textField === 'number') {
140+
if (__DEV__) {
141+
console.error(
142+
'registerInput must be called with a host component. Passing a react tag is deprecated.',
143+
);
144+
}
145+
146+
return;
147+
}
148+
149+
inputs.add(textField);
87150
}
88151

89-
function unregisterInput(textFieldID: number) {
90-
inputs.delete(textFieldID);
152+
function unregisterInput(textField: ComponentRef) {
153+
if (typeof textField === 'number') {
154+
if (__DEV__) {
155+
console.error(
156+
'unregisterInput must be called with a host component. Passing a react tag is deprecated.',
157+
);
158+
}
159+
160+
return;
161+
}
162+
inputs.delete(textField);
91163
}
92164

93-
function isTextInput(textFieldID: number): boolean {
94-
return inputs.has(textFieldID);
165+
function isTextInput(textField: ComponentRef): boolean {
166+
if (typeof textField === 'number') {
167+
if (__DEV__) {
168+
console.error(
169+
'isTextInput must be called with a host component. Passing a react tag is deprecated.',
170+
);
171+
}
172+
173+
return false;
174+
}
175+
176+
return inputs.has(textField);
95177
}
96178

97179
module.exports = {
180+
currentlyFocusedInput,
181+
focusInput,
182+
blurInput,
183+
98184
currentlyFocusedField,
99185
focusField,
100186
blurField,

Libraries/Components/TextInput/__tests__/TextInput-test.js

+19-7
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ describe('TextInput tests', () => {
8787

8888
expect(textInputRef.current.isFocused()).toBe(false);
8989
ReactNative.findNodeHandle = jest.fn().mockImplementation(ref => {
90+
if (ref == null) {
91+
return null;
92+
}
93+
9094
if (
9195
ref === textInputRef.current ||
9296
ref === textInputRef.current.getNativeRef()
@@ -97,13 +101,17 @@ describe('TextInput tests', () => {
97101
return 2;
98102
});
99103

100-
const inputTag = ReactNative.findNodeHandle(textInputRef.current);
101-
102-
TextInput.State.focusTextInput(inputTag);
104+
TextInput.State.focusTextInput(textInputRef.current);
103105
expect(textInputRef.current.isFocused()).toBe(true);
104-
expect(TextInput.State.currentlyFocusedField()).toBe(inputTag);
105-
TextInput.State.blurTextInput(inputTag);
106+
expect(TextInput.State.currentlyFocusedInput()).toBe(textInputRef.current);
107+
// This function is currently deprecated and will be removed in the future
108+
expect(TextInput.State.currentlyFocusedField()).toBe(
109+
ReactNative.findNodeHandle(textInputRef.current),
110+
);
111+
TextInput.State.blurTextInput(textInputRef.current);
106112
expect(textInputRef.current.isFocused()).toBe(false);
113+
expect(TextInput.State.currentlyFocusedInput()).toBe(null);
114+
// This function is currently deprecated and will be removed in the future
107115
expect(TextInput.State.currentlyFocusedField()).toBe(null);
108116
});
109117

@@ -141,16 +149,20 @@ describe('TextInput tests', () => {
141149
const inputTag1 = ReactNative.findNodeHandle(textInputRe1.current);
142150
const inputTag2 = ReactNative.findNodeHandle(textInputRe2.current);
143151

144-
TextInput.State.focusTextInput(inputTag1);
152+
TextInput.State.focusTextInput(textInputRe1.current);
145153

146154
expect(textInputRe1.current.isFocused()).toBe(true);
147155
expect(textInputRe2.current.isFocused()).toBe(false);
156+
expect(TextInput.State.currentlyFocusedInput()).toBe(textInputRe1.current);
157+
// This function is currently deprecated and will be removed in the future
148158
expect(TextInput.State.currentlyFocusedField()).toBe(inputTag1);
149159

150-
TextInput.State.focusTextInput(inputTag2);
160+
TextInput.State.focusTextInput(textInputRe2.current);
151161

152162
expect(textInputRe1.current.isFocused()).toBe(false);
153163
expect(textInputRe2.current.isFocused()).toBe(true);
164+
expect(TextInput.State.currentlyFocusedInput()).toBe(textInputRe2.current);
165+
// This function is currently deprecated and will be removed in the future
154166
expect(TextInput.State.currentlyFocusedField()).toBe(inputTag2);
155167
});
156168

Libraries/Utilities/dismissKeyboard.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
const TextInputState = require('../Components/TextInput/TextInputState');
1616

1717
function dismissKeyboard() {
18-
TextInputState.blurTextInput(TextInputState.currentlyFocusedField());
18+
TextInputState.blurTextInput(TextInputState.currentlyFocusedInput());
1919
}
2020

2121
module.exports = dismissKeyboard;

0 commit comments

Comments
 (0)