Skip to content

Commit 85e74eb

Browse files
committed
Log dev error for React.Fragment child in TouchableHighlight
Fixes #54933. TouchableHighlight injects the underlay style onto its single child via cloneElement. React.Fragment cannot accept that style, so React emits a generic "Invalid prop `style` supplied to `React.Fragment`" warning and the highlight effect is silently broken. Surface a clear console.error in __DEV__ naming the component, the constraint, and the fix (wrap in a View). Skip the cloneElement when the child is a Fragment so React's generic warning no longer fires on top. Render the Fragment unchanged so apps relying on this since 0.79 do not crash on upgrade. Pattern matches dev error logging used elsewhere in RN such as ScrollView and TextInputState.
1 parent 5f69a91 commit 85e74eb

2 files changed

Lines changed: 56 additions & 6 deletions

File tree

packages/react-native/Libraries/Components/Touchable/TouchableHighlight.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,12 @@ class TouchableHighlightImpl extends React.Component<
304304

305305
render(): React.Node {
306306
const child = React.Children.only<$FlowFixMe>(this.props.children);
307+
if (__DEV__ && child.type === React.Fragment) {
308+
console.error(
309+
'TouchableHighlight does not support React.Fragment as a child. ' +
310+
'Wrap the children in a single host element such as <View>.',
311+
);
312+
}
307313

308314
// BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
309315
// adopting `Pressability`, so preserve that behavior.
@@ -376,12 +382,14 @@ class TouchableHighlightImpl extends React.Component<
376382
testID={this.props.testID}
377383
ref={this.props.hostRef}
378384
{...eventHandlersWithoutBlurAndFocus}>
379-
{cloneElement(child, {
380-
style: StyleSheet.compose(
381-
child.props.style,
382-
this.state.extraStyles?.child,
383-
),
384-
})}
385+
{child.type === React.Fragment
386+
? child
387+
: cloneElement(child, {
388+
style: StyleSheet.compose(
389+
child.props.style,
390+
this.state.extraStyles?.child,
391+
),
392+
})}
385393
{__DEV__ ? (
386394
<PressabilityDebugView color="green" hitSlop={this.props.hitSlop} />
387395
) : null}

packages/react-native/Libraries/Components/Touchable/__tests__/TouchableHighlight-itest.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,48 @@ describe('<TouchableHighlight>', () => {
395395
</rn-view>,
396396
);
397397
});
398+
399+
// Regression test for #54933: a `React.Fragment` cannot accept the
400+
// underlay style applied via `cloneElement`, so the highlight effect
401+
// is silently broken. Render the Fragment unchanged and surface an
402+
// actionable dev error pointing the user at wrapping in <View>.
403+
it('logs a dev error when given a React.Fragment as a child', () => {
404+
const originalConsoleError = console.error;
405+
const mockConsoleError = jest.fn();
406+
// $FlowFixMe[cannot-write]
407+
console.error = mockConsoleError;
408+
409+
try {
410+
const root = Fantom.createRoot();
411+
412+
Fantom.runTask(() => {
413+
root.render(
414+
<TouchableHighlight>
415+
<React.Fragment>
416+
<Text>First</Text>
417+
<Text>Second</Text>
418+
</React.Fragment>
419+
</TouchableHighlight>,
420+
);
421+
});
422+
423+
expect(
424+
root.getRenderedOutput({props: ['accessible']}).toJSX(),
425+
).toEqual(
426+
<rn-view accessible="true">
427+
<rn-paragraph key="0">First</rn-paragraph>
428+
<rn-paragraph key="1">Second</rn-paragraph>
429+
</rn-view>,
430+
);
431+
expect(mockConsoleError).toHaveBeenCalledTimes(1);
432+
expect(mockConsoleError.mock.calls[0][0]).toContain(
433+
'TouchableHighlight does not support React.Fragment as a child',
434+
);
435+
} finally {
436+
// $FlowFixMe[cannot-write]
437+
console.error = originalConsoleError;
438+
}
439+
});
398440
});
399441
});
400442

0 commit comments

Comments
 (0)