Skip to content

Commit 301532b

Browse files
Olivier Bouilletreact-native-bot
Olivier Bouillet
authored andcommitted
fix: set text position should not reset component text (#49450)
Summary: fix: #49368 description is provided inside the ticket. When we use TextInput on ios and manage selection with the selection prop, TextInput is reset when we change selection. ## Changelog: [IOS] [FIXED] - Fix selection makes TextInput clear its content when using children Pull Request resolved: #49450 Test Plan: Tested with sample provided in ticket. I also test it with my app on both android and ios, but I cannot share video Reviewed By: sammy-SC Differential Revision: D69984616 Pulled By: cipolleschi fbshipit-source-id: a17169608f9df0ea1cb579e6038345f8e48bbc27
1 parent 23c9dbc commit 301532b

File tree

3 files changed

+187
-27
lines changed

3 files changed

+187
-27
lines changed

packages/react-native/Libraries/Components/TextInput/TextInput.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ function useTextInputStateSynchronization_STATE({
10191019
mostRecentEventCount: number,
10201020
selection: ?Selection,
10211021
inputRef: React.RefObject<null | HostInstance>,
1022-
text: string,
1022+
text?: string,
10231023
viewCommands: ViewCommands,
10241024
}): {
10251025
setLastNativeText: string => void,
@@ -1100,7 +1100,7 @@ function useTextInputStateSynchronization_REFS({
11001100
mostRecentEventCount: number,
11011101
selection: ?Selection,
11021102
inputRef: React.RefObject<null | HostInstance>,
1103-
text: string,
1103+
text?: string,
11041104
viewCommands: ViewCommands,
11051105
}): {
11061106
setLastNativeText: string => void,
@@ -1314,7 +1314,7 @@ function InternalTextInput(props: Props): React.Node {
13141314
? props.value
13151315
: typeof props.defaultValue === 'string'
13161316
? props.defaultValue
1317-
: '';
1317+
: undefined;
13181318

13191319
const viewCommands =
13201320
AndroidTextInputCommands ||

packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-test.js

+72-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @format
88
*/
99

10-
const {create} = require('../../../../jest/renderer');
10+
const {create, update} = require('../../../../jest/renderer');
1111
const ReactNativeFeatureFlags = require('../../../../src/private/featureflags/ReactNativeFeatureFlags');
1212
const ReactNative = require('../../../ReactNative/RendererProxy');
1313
const {
@@ -20,8 +20,14 @@ const ReactTestRenderer = require('react-test-renderer');
2020

2121
jest.unmock('../TextInput');
2222

23-
[true, false].forEach(useRefsForTextInputState => {
24-
describe(`TextInput tests (useRefsForTextInputState = ${useRefsForTextInputState}`, () => {
23+
[
24+
{useRefsForTextInputState: true, useTextChildren: true},
25+
{useRefsForTextInputState: false, useTextChildren: true},
26+
{useRefsForTextInputState: true, useTextChildren: false},
27+
{useRefsForTextInputState: false, useTextChildren: false},
28+
].forEach(testCase => {
29+
const {useRefsForTextInputState, useTextChildren} = testCase;
30+
describe(`TextInput tests (useRefsForTextInputState = ${useRefsForTextInputState}) useTextChildren = ${useTextChildren}`, () => {
2531
let input;
2632
let inputRef;
2733
let onChangeListener;
@@ -43,15 +49,16 @@ jest.unmock('../TextInput');
4349
return (
4450
<TextInput
4551
ref={inputRef}
46-
value={state.text}
52+
value={useTextChildren ? undefined : state.text}
4753
onChangeText={text => {
4854
onChangeTextListener(text);
4955
setState({text});
5056
}}
5157
onChange={event => {
5258
onChangeListener(event);
53-
}}
54-
/>
59+
}}>
60+
{useTextChildren ? state.text : undefined}
61+
</TextInput>
5562
);
5663
}
5764
const renderTree = await create(<TextInputWrapper />);
@@ -75,12 +82,20 @@ jest.unmock('../TextInput');
7582
);
7683
});
7784
it('calls onChange callbacks', () => {
78-
expect(input.props.value).toBe(initialValue);
85+
if (!useTextChildren) {
86+
expect(input.props.value).toBe(initialValue);
87+
} else {
88+
expect(input.props.children).toBe(initialValue);
89+
}
7990
const message = 'This is a test message';
8091
ReactTestRenderer.act(() => {
8192
enter(input, message);
8293
});
83-
expect(input.props.value).toBe(message);
94+
if (!useTextChildren) {
95+
expect(input.props.value).toBe(message);
96+
} else {
97+
expect(input.props.children).toBe(message);
98+
}
8499
expect(onChangeTextListener).toHaveBeenCalledWith(message);
85100
expect(onChangeListener).toHaveBeenCalledWith({
86101
nativeEvent: {text: message},
@@ -90,7 +105,12 @@ jest.unmock('../TextInput');
90105
async function createTextInput(extraProps) {
91106
const textInputRef = React.createRef(null);
92107
await create(
93-
<TextInput ref={textInputRef} value="value1" {...extraProps} />,
108+
<TextInput
109+
ref={textInputRef}
110+
value={useTextChildren ? undefined : 'value1'}
111+
{...extraProps}>
112+
{useTextChildren ? 'value1' : undefined}
113+
</TextInput>,
94114
);
95115
return textInputRef;
96116
}
@@ -134,14 +154,55 @@ jest.unmock('../TextInput');
134154
expect(TextInput.State.currentlyFocusedInput()).toBe(null);
135155
});
136156

157+
it('change selection keeps content', async () => {
158+
const defaultValue = 'value1';
159+
// create content
160+
let renderTree = await create(
161+
<TextInput
162+
value={useTextChildren ? undefined : defaultValue}
163+
position={{start: 1, end: 1}}>
164+
{useTextChildren ? defaultValue : undefined}
165+
</TextInput>,
166+
);
167+
input = renderTree.root.findByType(TextInput);
168+
expect(
169+
useTextChildren ? input.children[0].props.children : input.props.value,
170+
).toBe(defaultValue);
171+
expect(input.props.position.start).toBe(1);
172+
expect(input.props.position.end).toBe(1);
173+
174+
// update position
175+
renderTree = await update(
176+
renderTree,
177+
<TextInput
178+
value={useTextChildren ? undefined : defaultValue}
179+
position={{start: 2, end: 2}}>
180+
{useTextChildren ? defaultValue : undefined}
181+
</TextInput>,
182+
);
183+
expect(
184+
useTextChildren ? input.children[0].props.children : input.props.value,
185+
).toBe(defaultValue);
186+
expect(input.props.position.start).toBe(2);
187+
expect(input.props.position.end).toBe(2);
188+
});
189+
137190
it('should unfocus when other TextInput is focused', async () => {
138191
const textInputRe1 = React.createRef(null);
139192
const textInputRe2 = React.createRef(null);
140193

141194
await create(
142195
<>
143-
<TextInput ref={textInputRe1} value="value1" />
144-
<TextInput ref={textInputRe2} value="value2" />
196+
<TextInput
197+
ref={textInputRe1}
198+
value={useTextChildren ? undefined : 'value1'}>
199+
{useTextChildren ? 'value1' : undefined}
200+
</TextInput>
201+
<TextInput
202+
ref={textInputRe2}
203+
value={useTextChildren ? undefined : 'value2'}>
204+
{useTextChildren ? 'value2' : undefined}
205+
</TextInput>
145206
</>,
146207
);
147208
ReactNative.findNodeHandle = jest.fn().mockImplementation(ref => {
@@ -210,7 +271,6 @@ jest.unmock('../TextInput');
210271
rejectResponderTermination={true}
211272
selection={null}
212273
submitBehavior="blurAndSubmit"
213-
text=""
214274
textContentType="emailAddress"
215275
underlineColorAndroid="transparent"
216276
/>
@@ -255,7 +315,6 @@ jest.unmock('../TextInput');
255315
rejectResponderTermination={true}
256316
selection={null}
257317
submitBehavior="blurAndSubmit"
258-
text=""
259318
underlineColorAndroid="transparent"
260319
/>
261320
`);
@@ -301,7 +360,6 @@ jest.unmock('../TextInput');
301360
selection={null}
302361
submitBehavior="blurAndSubmit"
303362
testID="testID"
304-
text=""
305363
underlineColorAndroid="transparent"
306364
/>
307365
`);
@@ -432,7 +490,6 @@ jest.unmock('../TextInput');
432490
role="main"
433491
selection={null}
434492
submitBehavior="blurAndSubmit"
435-
text=""
436493
underlineColorAndroid="transparent"
437494
/>
438495
`);
@@ -489,7 +546,6 @@ jest.unmock('../TextInput');
489546
]
490547
}
491548
submitBehavior="blurAndSubmit"
492-
text=""
493549
underlineColorAndroid="transparent"
494550
/>
495551
`);

packages/react-native/Libraries/Components/TextInput/__tests__/__snapshots__/TextInput-test.js.snap

+112-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`TextInput tests (useRefsForTextInputState = false should render as expected: should deep render when mocked (please verify output manually) 1`] = `
3+
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = false should render as expected: should deep render when mocked (please verify output manually) 1`] = `
44
<RCTSinglelineTextInputView
55
accessible={true}
66
allowFontScaling={true}
@@ -23,12 +23,11 @@ exports[`TextInput tests (useRefsForTextInputState = false should render as expe
2323
rejectResponderTermination={true}
2424
selection={null}
2525
submitBehavior="blurAndSubmit"
26-
text=""
2726
underlineColorAndroid="transparent"
2827
/>
2928
`;
3029

31-
exports[`TextInput tests (useRefsForTextInputState = false should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
30+
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = false should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
3231
<RCTSinglelineTextInputView
3332
accessible={true}
3433
allowFontScaling={true}
@@ -51,12 +50,11 @@ exports[`TextInput tests (useRefsForTextInputState = false should render as expe
5150
rejectResponderTermination={true}
5251
selection={null}
5352
submitBehavior="blurAndSubmit"
54-
text=""
5553
underlineColorAndroid="transparent"
5654
/>
5755
`;
5856

59-
exports[`TextInput tests (useRefsForTextInputState = true should render as expected: should deep render when mocked (please verify output manually) 1`] = `
57+
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = true should render as expected: should deep render when mocked (please verify output manually) 1`] = `
6058
<RCTSinglelineTextInputView
6159
accessible={true}
6260
allowFontScaling={true}
@@ -79,12 +77,119 @@ exports[`TextInput tests (useRefsForTextInputState = true should render as expec
7977
rejectResponderTermination={true}
8078
selection={null}
8179
submitBehavior="blurAndSubmit"
82-
text=""
8380
underlineColorAndroid="transparent"
8481
/>
8582
`;
8683

87-
exports[`TextInput tests (useRefsForTextInputState = true should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
84+
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = true should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
85+
<RCTSinglelineTextInputView
86+
accessible={true}
87+
allowFontScaling={true}
88+
focusable={true}
89+
forwardedRef={null}
90+
mostRecentEventCount={0}
91+
onBlur={[Function]}
92+
onChange={[Function]}
93+
onClick={[Function]}
94+
onFocus={[Function]}
95+
onResponderGrant={[Function]}
96+
onResponderMove={[Function]}
97+
onResponderRelease={[Function]}
98+
onResponderTerminate={[Function]}
99+
onResponderTerminationRequest={[Function]}
100+
onScroll={[Function]}
101+
onSelectionChange={[Function]}
102+
onSelectionChangeShouldSetResponder={[Function]}
103+
onStartShouldSetResponder={[Function]}
104+
rejectResponderTermination={true}
105+
selection={null}
106+
submitBehavior="blurAndSubmit"
107+
underlineColorAndroid="transparent"
108+
/>
109+
`;
110+
111+
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = false should render as expected: should deep render when mocked (please verify output manually) 1`] = `
112+
<RCTSinglelineTextInputView
113+
accessible={true}
114+
allowFontScaling={true}
115+
focusable={true}
116+
forwardedRef={null}
117+
mostRecentEventCount={0}
118+
onBlur={[Function]}
119+
onChange={[Function]}
120+
onClick={[Function]}
121+
onFocus={[Function]}
122+
onResponderGrant={[Function]}
123+
onResponderMove={[Function]}
124+
onResponderRelease={[Function]}
125+
onResponderTerminate={[Function]}
126+
onResponderTerminationRequest={[Function]}
127+
onScroll={[Function]}
128+
onSelectionChange={[Function]}
129+
onSelectionChangeShouldSetResponder={[Function]}
130+
onStartShouldSetResponder={[Function]}
131+
rejectResponderTermination={true}
132+
selection={null}
133+
submitBehavior="blurAndSubmit"
134+
underlineColorAndroid="transparent"
135+
/>
136+
`;
137+
138+
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = false should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
139+
<RCTSinglelineTextInputView
140+
accessible={true}
141+
allowFontScaling={true}
142+
focusable={true}
143+
forwardedRef={null}
144+
mostRecentEventCount={0}
145+
onBlur={[Function]}
146+
onChange={[Function]}
147+
onClick={[Function]}
148+
onFocus={[Function]}
149+
onResponderGrant={[Function]}
150+
onResponderMove={[Function]}
151+
onResponderRelease={[Function]}
152+
onResponderTerminate={[Function]}
153+
onResponderTerminationRequest={[Function]}
154+
onScroll={[Function]}
155+
onSelectionChange={[Function]}
156+
onSelectionChangeShouldSetResponder={[Function]}
157+
onStartShouldSetResponder={[Function]}
158+
rejectResponderTermination={true}
159+
selection={null}
160+
submitBehavior="blurAndSubmit"
161+
underlineColorAndroid="transparent"
162+
/>
163+
`;
164+
165+
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = true should render as expected: should deep render when mocked (please verify output manually) 1`] = `
166+
<RCTSinglelineTextInputView
167+
accessible={true}
168+
allowFontScaling={true}
169+
focusable={true}
170+
forwardedRef={null}
171+
mostRecentEventCount={0}
172+
onBlur={[Function]}
173+
onChange={[Function]}
174+
onClick={[Function]}
175+
onFocus={[Function]}
176+
onResponderGrant={[Function]}
177+
onResponderMove={[Function]}
178+
onResponderRelease={[Function]}
179+
onResponderTerminate={[Function]}
180+
onResponderTerminationRequest={[Function]}
181+
onScroll={[Function]}
182+
onSelectionChange={[Function]}
183+
onSelectionChangeShouldSetResponder={[Function]}
184+
onStartShouldSetResponder={[Function]}
185+
rejectResponderTermination={true}
186+
selection={null}
187+
submitBehavior="blurAndSubmit"
188+
underlineColorAndroid="transparent"
189+
/>
190+
`;
191+
192+
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = true should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
88193
<RCTSinglelineTextInputView
89194
accessible={true}
90195
allowFontScaling={true}
@@ -107,7 +212,6 @@ exports[`TextInput tests (useRefsForTextInputState = true should render as expec
107212
rejectResponderTermination={true}
108213
selection={null}
109214
submitBehavior="blurAndSubmit"
110-
text=""
111215
underlineColorAndroid="transparent"
112216
/>
113217
`;

0 commit comments

Comments
 (0)