Skip to content

Commit b9211c1

Browse files
fix: support ReactNode children in TouchableRipple
1 parent 8b6b5e5 commit b9211c1

3 files changed

Lines changed: 72 additions & 18 deletions

File tree

src/components/TouchableRipple/TouchableRipple.native.tsx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
ColorValue,
1111
} from 'react-native';
1212

13-
import type { PressableProps } from './Pressable';
13+
import type { PressableProps, PressableStateCallbackType } from './Pressable';
1414
import { Pressable } from './Pressable';
1515
import { getTouchableRippleColors } from './utils';
1616
import { Settings, SettingsContext } from '../../core/settings';
@@ -33,7 +33,9 @@ export type Props = PressableProps & {
3333
onPressOut?: (e: GestureResponderEvent) => void;
3434
rippleColor?: ColorValue;
3535
underlayColor?: string;
36-
children: React.ReactNode;
36+
children:
37+
| ((state: PressableStateCallbackType) => React.ReactNode)
38+
| React.ReactNode;
3739
style?: StyleProp<ViewStyle>;
3840
theme?: ThemeProp;
3941
};
@@ -73,6 +75,18 @@ const TouchableRipple = (
7375
underlayColor,
7476
});
7577

78+
const getStyle = (state: unknown) => [
79+
borderless && styles.overflowHidden,
80+
typeof style === 'function'
81+
? (style as (state: unknown) => StyleProp<ViewStyle>)(state)
82+
: style,
83+
];
84+
85+
const getChildren = (state: unknown) =>
86+
typeof children === 'function'
87+
? (children as (state: unknown) => React.ReactNode)(state)
88+
: children;
89+
7690
// A workaround for ripple on Android P is to use useForeground + overflow: 'hidden'
7791
// https://github.com/facebook/react-native/issues/6480
7892
const useForeground =
@@ -94,24 +108,19 @@ const TouchableRipple = (
94108
{...rest}
95109
ref={ref}
96110
disabled={disabled}
97-
style={[borderless && styles.overflowHidden, style]}
111+
style={getStyle}
98112
android_ripple={androidRipple}
99113
>
100-
{React.Children.only(children)}
114+
{getChildren}
101115
</Pressable>
102116
);
103117
}
104118

105119
return (
106-
<Pressable
107-
{...rest}
108-
ref={ref}
109-
disabled={disabled}
110-
style={[borderless && styles.overflowHidden, style]}
111-
>
112-
{({ pressed }) => (
120+
<Pressable {...rest} ref={ref} disabled={disabled} style={getStyle}>
121+
{(state) => (
113122
<>
114-
{pressed && rippleEffectEnabled && (
123+
{state.pressed && rippleEffectEnabled && (
115124
<View
116125
testID="touchable-ripple-underlay"
117126
style={[
@@ -120,7 +129,7 @@ const TouchableRipple = (
120129
]}
121130
/>
122131
)}
123-
{React.Children.only(children)}
132+
{getChildren(state)}
124133
</>
125134
)}
126135
</Pressable>

src/components/TouchableRipple/TouchableRipple.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,7 @@ const TouchableRipple = (
286286
typeof style === 'function' ? style(state) : style,
287287
]}
288288
>
289-
{(state) =>
290-
React.Children.only(
291-
typeof children === 'function' ? children(state) : children
292-
)
293-
}
289+
{(state) => (typeof children === 'function' ? children(state) : children)}
294290
</Pressable>
295291
);
296292
};

src/components/__tests__/TouchableRipple.test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,55 @@ describe('TouchableRipple', () => {
4242
expect(onPress).not.toHaveBeenCalled();
4343
});
4444

45+
it('supports children as a function', () => {
46+
const children = ({ pressed }: { pressed: boolean }) => (
47+
<Text>{pressed ? 'Pressed' : 'Button'}</Text>
48+
);
49+
50+
const { getByText } = render(
51+
<TouchableRipple onPress={jest.fn()}>{children}</TouchableRipple>
52+
);
53+
54+
expect(getByText('Button')).toBeTruthy();
55+
});
56+
57+
it('supports children as an array of nodes', () => {
58+
const { getByText } = render(
59+
<TouchableRipple onPress={jest.fn()}>
60+
{[<Text key="a">Button A</Text>, <Text key="b">Button B</Text>]}
61+
</TouchableRipple>
62+
);
63+
64+
expect(getByText('Button A')).toBeTruthy();
65+
expect(getByText('Button B')).toBeTruthy();
66+
});
67+
68+
it('supports function children returning an array of nodes', () => {
69+
const children = ({ pressed }: { pressed: boolean }) => [
70+
<Text key="a">{pressed ? 'Pressed A' : 'Button A'}</Text>,
71+
<Text key="b">{pressed ? 'Pressed B' : 'Button B'}</Text>,
72+
];
73+
74+
const { getByText } = render(
75+
<TouchableRipple onPress={jest.fn()}>{children}</TouchableRipple>
76+
);
77+
78+
expect(getByText('Button A')).toBeTruthy();
79+
expect(getByText('Button B')).toBeTruthy();
80+
});
81+
82+
it('supports style as a function', () => {
83+
const style = jest.fn(() => ({ opacity: 0.5 }));
84+
85+
render(
86+
<TouchableRipple onPress={jest.fn()} style={style as any}>
87+
<Text>Button</Text>
88+
</TouchableRipple>
89+
);
90+
91+
expect(style).toHaveBeenCalled();
92+
});
93+
4594
describe('on iOS', () => {
4695
Platform.OS = 'ios';
4796

0 commit comments

Comments
 (0)