Skip to content

Commit c802d29

Browse files
authored
Use HostContext to warn about invalid View/Text nesting (#12766)
1 parent c5d3104 commit c802d29

File tree

8 files changed

+352
-141
lines changed

8 files changed

+352
-141
lines changed

packages/react-native-renderer/src/ReactFabricRenderer.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
2222
import ReactFiberReconciler from 'react-reconciler';
2323

2424
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
25-
import emptyObject from 'fbjs/lib/emptyObject';
25+
import invariant from 'fbjs/lib/invariant';
2626

2727
// Modules provided by RN:
2828
import TextInputState from 'TextInputState';
@@ -35,6 +35,10 @@ import UIManager from 'UIManager';
3535
// This means that they never overlap.
3636
let nextReactTag = 2;
3737

38+
type HostContext = $ReadOnly<{|
39+
isInAParentText: boolean,
40+
|}>;
41+
3842
/**
3943
* This is used for refs on host components.
4044
*/
@@ -135,7 +139,7 @@ const ReactFabricRenderer = ReactFiberReconciler({
135139
type: string,
136140
props: Props,
137141
rootContainerInstance: Container,
138-
hostContext: {},
142+
hostContext: HostContext,
139143
internalInstanceHandle: Object,
140144
): Instance {
141145
const tag = nextReactTag;
@@ -151,6 +155,11 @@ const ReactFabricRenderer = ReactFiberReconciler({
151155
}
152156
}
153157

158+
invariant(
159+
type !== 'RCTView' || !hostContext.isInAParentText,
160+
'Nesting of <View> within <Text> is not currently supported.',
161+
);
162+
154163
const updatePayload = ReactNativeAttributePayload.create(
155164
props,
156165
viewConfig.validAttributes,
@@ -175,9 +184,14 @@ const ReactFabricRenderer = ReactFiberReconciler({
175184
createTextInstance(
176185
text: string,
177186
rootContainerInstance: Container,
178-
hostContext: {},
187+
hostContext: HostContext,
179188
internalInstanceHandle: Object,
180189
): TextInstance {
190+
invariant(
191+
hostContext.isInAParentText,
192+
'Text strings must be rendered within a <Text> component.',
193+
);
194+
181195
const tag = nextReactTag;
182196
nextReactTag += 2;
183197

@@ -203,12 +217,27 @@ const ReactFabricRenderer = ReactFiberReconciler({
203217
return false;
204218
},
205219

206-
getRootHostContext(): {} {
207-
return emptyObject;
220+
getRootHostContext(rootContainerInstance: Container): HostContext {
221+
return {isInAParentText: false};
208222
},
209223

210-
getChildHostContext(): {} {
211-
return emptyObject;
224+
getChildHostContext(
225+
parentHostContext: HostContext,
226+
type: string,
227+
): HostContext {
228+
const prevIsInAParentText = parentHostContext.isInAParentText;
229+
const isInAParentText =
230+
type === 'AndroidTextInput' || // Android
231+
type === 'RCTMultilineTextInputView' || // iOS
232+
type === 'RCTSinglelineTextInputView' || // iOS
233+
type === 'RCTText' ||
234+
type === 'RCTVirtualText';
235+
236+
if (prevIsInAParentText !== isInAParentText) {
237+
return {isInAParentText};
238+
} else {
239+
return parentHostContext;
240+
}
212241
},
213242

214243
getPublicInstance(instance) {
@@ -230,7 +259,7 @@ const ReactFabricRenderer = ReactFiberReconciler({
230259
oldProps: Props,
231260
newProps: Props,
232261
rootContainerInstance: Container,
233-
hostContext: {},
262+
hostContext: HostContext,
234263
): null | Object {
235264
const viewConfig = instance.canonical.viewConfig;
236265
const updatePayload = ReactNativeAttributePayload.diff(

packages/react-native-renderer/src/ReactNativeFiberRenderer.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes';
1212
import ReactFiberReconciler from 'react-reconciler';
1313
import emptyObject from 'fbjs/lib/emptyObject';
1414
import invariant from 'fbjs/lib/invariant';
15+
1516
// Modules provided by RN:
1617
import UIManager from 'UIManager';
1718
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
@@ -35,6 +36,10 @@ export type Instance = {
3536
type Props = Object;
3637
type TextInstance = number;
3738

39+
type HostContext = $ReadOnly<{|
40+
isInAParentText: boolean,
41+
|}>;
42+
3843
// Counter for uniquely identifying views.
3944
// % 10 === 1 means it is a rootTag.
4045
// % 2 === 0 means it is a Fabric tag.
@@ -71,7 +76,7 @@ const NativeRenderer = ReactFiberReconciler({
7176
type: string,
7277
props: Props,
7378
rootContainerInstance: Container,
74-
hostContext: {},
79+
hostContext: HostContext,
7580
internalInstanceHandle: Object,
7681
): Instance {
7782
const tag = allocateTag();
@@ -85,6 +90,11 @@ const NativeRenderer = ReactFiberReconciler({
8590
}
8691
}
8792

93+
invariant(
94+
type !== 'RCTView' || !hostContext.isInAParentText,
95+
'Nesting of <View> within <Text> is not currently supported.',
96+
);
97+
8898
const updatePayload = ReactNativeAttributePayload.create(
8999
props,
90100
viewConfig.validAttributes,
@@ -110,9 +120,14 @@ const NativeRenderer = ReactFiberReconciler({
110120
createTextInstance(
111121
text: string,
112122
rootContainerInstance: Container,
113-
hostContext: {},
123+
hostContext: HostContext,
114124
internalInstanceHandle: Object,
115125
): TextInstance {
126+
invariant(
127+
hostContext.isInAParentText,
128+
'Text strings must be rendered within a <Text> component.',
129+
);
130+
116131
const tag = allocateTag();
117132

118133
UIManager.createView(
@@ -155,12 +170,27 @@ const NativeRenderer = ReactFiberReconciler({
155170
return false;
156171
},
157172

158-
getRootHostContext(): {} {
159-
return emptyObject;
173+
getRootHostContext(rootContainerInstance: Container): HostContext {
174+
return {isInAParentText: false};
160175
},
161176

162-
getChildHostContext(): {} {
163-
return emptyObject;
177+
getChildHostContext(
178+
parentHostContext: HostContext,
179+
type: string,
180+
): HostContext {
181+
const prevIsInAParentText = parentHostContext.isInAParentText;
182+
const isInAParentText =
183+
type === 'AndroidTextInput' || // Android
184+
type === 'RCTMultilineTextInputView' || // iOS
185+
type === 'RCTSinglelineTextInputView' || // iOS
186+
type === 'RCTText' ||
187+
type === 'RCTVirtualText';
188+
189+
if (prevIsInAParentText !== isInAParentText) {
190+
return {isInAParentText};
191+
} else {
192+
return parentHostContext;
193+
}
164194
},
165195

166196
getPublicInstance(instance) {
@@ -181,7 +211,7 @@ const NativeRenderer = ReactFiberReconciler({
181211
oldProps: Props,
182212
newProps: Props,
183213
rootContainerInstance: Container,
184-
hostContext: {},
214+
hostContext: HostContext,
185215
): null | Object {
186216
return emptyObject;
187217
},

0 commit comments

Comments
 (0)