Skip to content

Commit d7b5314

Browse files
qflenmeta-codesync[bot]
authored andcommitted
Skip cloneElement for Fragment children in TouchableHighlight (#56539)
Summary: Fixes #54933. Passing a `React.Fragment` as the child of `TouchableHighlight` currently produces this warning: Warning: Invalid prop `style` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props. This happens because `TouchableHighlight` unconditionally calls `cloneElement(child, {style})` on its single child, including when that child is a Fragment. This PR skips that clone when `child.type === React.Fragment` and renders the Fragment as-is. That avoids passing an invalid `style` prop to `React.Fragment` while preserving the existing render path for Fragment children. ## Changelog: [General] [Fixed] - Suppress `React.Fragment` style warning when used as a child of `TouchableHighlight` Pull Request resolved: #56539 Test Plan: - Added a Fantom regression test in `TouchableHighlight-itest.js` that renders `<TouchableHighlight><Fragment>...</Fragment></TouchableHighlight>` - Verified the expected rendered output - Verified `console.error` is not called - `yarn flow focus-check packages/react-native/Libraries/Components/Touchable/TouchableHighlight.js` - `yarn lint` on the changed files - `yarn prettier --list-different` on the changed files Reviewed By: GijsWeterings Differential Revision: D104224036 Pulled By: javache fbshipit-source-id: 17289926bc62cda84decaf7154183ff355a03d21
1 parent 41fc7fd commit d7b5314

2 files changed

Lines changed: 58 additions & 6 deletions

File tree

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Pressability, {
1919
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
2020
import StyleSheet, {type ViewStyleProp} from '../../StyleSheet/StyleSheet';
2121
import Platform from '../../Utilities/Platform';
22+
import warnOnce from '../../Utilities/warnOnce';
2223
import * as React from 'react';
2324
import {cloneElement} from 'react';
2425

@@ -305,6 +306,13 @@ class TouchableHighlightImpl extends React.Component<
305306

306307
render(): React.Node {
307308
const child = React.Children.only<$FlowFixMe>(this.props.children);
309+
if (__DEV__ && child.type === React.Fragment) {
310+
warnOnce(
311+
'TouchableHighlight-fragment',
312+
'TouchableHighlight does not support React.Fragment as a child. ' +
313+
'Wrap the children in a single host element such as <View>.',
314+
);
315+
}
308316

309317
// BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
310318
// adopting `Pressability`, so preserve that behavior.
@@ -377,12 +385,14 @@ class TouchableHighlightImpl extends React.Component<
377385
testID={this.props.testID}
378386
ref={this.props.hostRef}
379387
{...eventHandlersWithoutBlurAndFocus}>
380-
{cloneElement(child, {
381-
style: StyleSheet.compose(
382-
child.props.style,
383-
this.state.extraStyles?.child,
384-
),
385-
})}
388+
{child.type === React.Fragment
389+
? child
390+
: cloneElement(child, {
391+
style: StyleSheet.compose(
392+
child.props.style,
393+
this.state.extraStyles?.child,
394+
),
395+
})}
386396
{__DEV__ ? (
387397
<PressabilityDebugView color="green" hitSlop={this.props.hitSlop} />
388398
) : 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 warning pointing the user at wrapping in <View>.
403+
it('logs a dev warning when given a React.Fragment as a child', () => {
404+
const originalConsoleWarn = console.warn;
405+
const mockConsoleWarn = jest.fn();
406+
// $FlowExpectedError[cannot-write]
407+
console.warn = mockConsoleWarn;
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(mockConsoleWarn).toHaveBeenCalledTimes(1);
432+
expect(mockConsoleWarn.mock.calls[0][0]).toContain(
433+
'TouchableHighlight does not support React.Fragment as a child',
434+
);
435+
} finally {
436+
// $FlowExpectedError[cannot-write]
437+
console.warn = originalConsoleWarn;
438+
}
439+
});
398440
});
399441
});
400442

0 commit comments

Comments
 (0)