Skip to content

Commit 5444e7d

Browse files
gabrieldonadelOlimpiaZurek
authored andcommitted
feat: Add id prop to Text, TouchableWithoutFeedback and View components (facebook#34522)
Summary: This adds the `id` prop to `Text`, `TouchableWithoutFeedback` and `View` components as requested on facebook#34424 mapping the existing `nativeID` prop to `id`. As this components are inherited by others this also adds the `id` prop support to `Image`, `TouchableBounce`, `TouchableHighlight`, `TouchableOpacity` and `TextInput`. This PR also adds android tests ensuring that the `id` property is passed to the native view via the `nativeID` prop, these tests were based on the existing `nativeID` tests ([NativeIdTestCase.java](https://github.com/facebook/react-native/blob/main/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java)). ## Changelog [General] [Added] - Add id prop to Text, TouchableWithoutFeedback and View components Pull Request resolved: facebook#34522 Test Plan: Ensure that the new `id` prop android tests pass on CircleCI Reviewed By: lunaleaps Differential Revision: D39089639 Pulled By: cipolleschi fbshipit-source-id: 884fb2461720835ca5048004fa79096dac89c51c
1 parent 9883330 commit 5444e7d

File tree

8 files changed

+193
-0
lines changed

8 files changed

+193
-0
lines changed

Libraries/Components/Touchable/TouchableWithoutFeedback.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type Props = $ReadOnly<{|
6666
disabled?: ?boolean,
6767
focusable?: ?boolean,
6868
hitSlop?: ?EdgeInsetsProp,
69+
id?: string,
6970
importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'),
7071
nativeID?: ?string,
7172
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed,
@@ -169,6 +170,7 @@ class TouchableWithoutFeedback extends React.Component<Props, State> {
169170
ariaLive === 'off'
170171
? 'none'
171172
: ariaLive ?? this.props.accessibilityLiveRegion,
173+
nativeID: this.props.id ?? this.props.nativeID,
172174
};
173175
for (const prop of PASSTHROUGH_PROPS) {
174176
if (this.props[prop] !== undefined) {

Libraries/Components/View/View.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ const View: React.AbstractComponent<
3838
'aria-label': ariaLabel,
3939
'aria-hidden': ariaHidden,
4040
focusable,
41+
id,
4142
importantForAccessibility,
43+
nativeID,
4244
pointerEvents,
4345
role,
4446
style,
@@ -164,6 +166,7 @@ const View: React.AbstractComponent<
164166
? 'no-hide-descendants'
165167
: importantForAccessibility
166168
}
169+
nativeID={id ?? nativeID}
167170
{...restWithDefaultProps}
168171
style={style}
169172
pointerEvents={newPointerEvents}

Libraries/Components/View/ViewPropTypes.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,15 @@ export type ViewProps = $ReadOnly<{|
546546
*/
547547
collapsable?: ?boolean,
548548

549+
/**
550+
* Used to locate this view from native classes.
551+
*
552+
* > This disables the 'layout-only view removal' optimization for this view!
553+
*
554+
* See https://reactnative.dev/docs/view#id
555+
*/
556+
id?: string,
557+
549558
/**
550559
* Used to locate this view in end-to-end tests.
551560
*

Libraries/Text/Text.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const Text: React.AbstractComponent<
4242
'aria-label': ariaLabel,
4343
'aria-selected': ariaSelected,
4444
ellipsizeMode,
45+
id,
46+
nativeID,
4547
onLongPress,
4648
onPress,
4749
onPressIn,
@@ -208,6 +210,7 @@ const Text: React.AbstractComponent<
208210
isHighlighted={isHighlighted}
209211
isPressable={isPressable}
210212
selectable={_selectable}
213+
nativeID={id ?? nativeID}
211214
numberOfLines={numberOfLines}
212215
selectionColor={selectionColor}
213216
style={style}
@@ -226,6 +229,7 @@ const Text: React.AbstractComponent<
226229
allowFontScaling={allowFontScaling !== false}
227230
ellipsizeMode={ellipsizeMode ?? 'tail'}
228231
isHighlighted={isHighlighted}
232+
nativeID={id ?? nativeID}
229233
numberOfLines={numberOfLines}
230234
selectionColor={selectionColor}
231235
style={style}

Libraries/Text/TextProps.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ export type TextProps = $ReadOnly<{|
9999
*/
100100
ellipsizeMode?: ?('clip' | 'head' | 'middle' | 'tail'),
101101

102+
/**
103+
* Used to locate this view from native code.
104+
*
105+
* See https://reactnative.dev/docs/text#nativeid
106+
*/
107+
id?: string,
108+
102109
/**
103110
* Specifies largest possible scale a font can reach when `allowFontScaling` is enabled.
104111
* Possible values:
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.tests;
9+
10+
import android.view.View;
11+
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
12+
import com.facebook.react.uimanager.util.ReactFindViewUtil;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
16+
/**
17+
* Tests that the 'id' property can be set on various views. The 'id' property is used to reference
18+
* react managed views from native code.
19+
*/
20+
public class IdTestCase extends ReactAppInstrumentationTestCase {
21+
22+
@Override
23+
protected String getReactApplicationKeyUnderTest() {
24+
return "IdTestApp";
25+
}
26+
27+
private final List<String> viewTags =
28+
Arrays.asList(
29+
"Image",
30+
"Text",
31+
"TouchableBounce",
32+
"TouchableHighlight",
33+
"TouchableOpacity",
34+
"TouchableWithoutFeedback",
35+
"TextInput",
36+
"View");
37+
38+
private boolean mViewFound;
39+
40+
@Override
41+
protected void setUp() throws Exception {
42+
mViewFound = false;
43+
ReactFindViewUtil.addViewListener(
44+
new ReactFindViewUtil.OnViewFoundListener() {
45+
@Override
46+
public String getNativeId() {
47+
return viewTags.get(0);
48+
}
49+
50+
@Override
51+
public void onViewFound(View view) {
52+
mViewFound = true;
53+
}
54+
});
55+
super.setUp();
56+
}
57+
58+
public void testPropertyIsSetForViews() {
59+
for (String nativeId : viewTags) {
60+
View viewWithTag = ReactFindViewUtil.findView(getActivity().getRootView(), nativeId);
61+
assertNotNull(
62+
"View with id " + nativeId + " was not found. Check IdTestModule.js.", viewWithTag);
63+
}
64+
}
65+
66+
public void testViewListener() {
67+
assertTrue("OnViewFound callback was never invoked", mViewFound);
68+
}
69+
70+
public void testFindView() {
71+
mViewFound = false;
72+
ReactFindViewUtil.findView(
73+
getActivity().getRootView(),
74+
new ReactFindViewUtil.OnViewFoundListener() {
75+
@Override
76+
public String getNativeId() {
77+
return viewTags.get(0);
78+
}
79+
80+
@Override
81+
public void onViewFound(View view) {
82+
mViewFound = true;
83+
}
84+
});
85+
assertTrue(
86+
"OnViewFound callback should have successfully been invoked synchronously", mViewFound);
87+
}
88+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict-local
9+
*/
10+
11+
'use strict';
12+
13+
const React = require('react');
14+
const TouchableBounce = require('react-native/Libraries/Components/Touchable/TouchableBounce');
15+
16+
const {
17+
Image,
18+
StyleSheet,
19+
Text,
20+
TextInput,
21+
TouchableHighlight,
22+
TouchableOpacity,
23+
TouchableWithoutFeedback,
24+
View,
25+
} = require('react-native');
26+
27+
/**
28+
* All the views implemented on Android, each with the id property set.
29+
* We test that:
30+
* - The app renders fine
31+
* - The id property is passed to the native views via the nativeID property
32+
*/
33+
class IdTestApp extends React.Component<{...}> {
34+
render(): React.Node {
35+
const uri =
36+
'data:image/gif;base64,' +
37+
'R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapy' +
38+
'uvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/' +
39+
'TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5' +
40+
'iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97V' +
41+
'riy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7';
42+
return (
43+
<View>
44+
<Image id="Image" source={{uri: uri}} style={styles.base} />
45+
<Text id="Text">text</Text>
46+
<TextInput id="TextInput" value="Text input" />
47+
<TouchableBounce id="TouchableBounce">
48+
<Text>TouchableBounce</Text>
49+
</TouchableBounce>
50+
<TouchableHighlight id="TouchableHighlight">
51+
<Text>TouchableHighlight</Text>
52+
</TouchableHighlight>
53+
<TouchableOpacity id="TouchableOpacity">
54+
<Text>TouchableOpacity</Text>
55+
</TouchableOpacity>
56+
<TouchableWithoutFeedback id="TouchableWithoutFeedback">
57+
<View>
58+
<Text>TouchableWithoutFeedback</Text>
59+
</View>
60+
</TouchableWithoutFeedback>
61+
<View id="View" />
62+
</View>
63+
);
64+
}
65+
}
66+
67+
const styles = StyleSheet.create({
68+
base: {
69+
width: 150,
70+
height: 50,
71+
},
72+
});
73+
74+
module.exports = {
75+
IdTestApp,
76+
};

ReactAndroid/src/androidTest/js/TestApps.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ const apps = [
4848
component: () =>
4949
require('./ScrollViewTestModule').HorizontalScrollViewTestApp,
5050
},
51+
{
52+
appKey: 'IdTestApp',
53+
component: () => require('./IdTestModule').IdTestApp,
54+
},
5155
{
5256
appKey: 'ImageOverlayColorTestApp',
5357
component: () => require('./ImageOverlayColorTestApp'),

0 commit comments

Comments
 (0)