Skip to content

Commit 93533fd

Browse files
authored
Add ref property to Buttons (#2903)
## Description Currently our components (`BaseButton`, `RectButton` and `BorderlessButton`) don't support `refs`, so it is impossible to use methods like `measure`. This PR adds wrapper to these components, so that they are now exported as `ForwardRef`. Fixes #2894 ## Test plan <details> <summary> Tested on slightly modified code from issue </summary> ```jsx import React, { useRef } from 'react'; import { Text, StyleSheet } from 'react-native'; import { BaseButton, BorderlessButton, GestureHandlerRootView, RectButton, } from 'react-native-gesture-handler'; export default function App() { const rectButtonRef = useRef(null); const borderlessButtonRef = useRef(null); const baseButtonRef = useRef(null); const handlePress = () => { try { baseButtonRef.current?.measure?.((x, y, width, height) => { console.log('baseButtonRef', x, y, width, height); }); rectButtonRef.current?.measure?.((x, y) => { console.log('rectButtonRef', x, y); }); borderlessButtonRef.current?.measure?.((x, y) => { console.log('borderlessButtonRef', x, y); }); } catch (e) { console.error(e); } }; return ( <GestureHandlerRootView style={styles.container}> <RectButton onPress={handlePress} style={styles.button}> <Text style={styles.text}>Press me</Text> </RectButton> <BaseButton ref={baseButtonRef} style={styles.button}> <Text style={styles.text}>Test</Text> </BaseButton> <BorderlessButton ref={borderlessButtonRef} style={styles.button}> <Text style={styles.text}>Test</Text> </BorderlessButton> <RectButton ref={rectButtonRef} style={styles.button}> <Text style={styles.text}>Test</Text> </RectButton> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 20, }, button: { justifyContent: 'center', alignItems: 'center', borderRadius: 5, backgroundColor: 'grey', paddingHorizontal: 20, paddingVertical: 10, }, text: { color: 'white', }, }); ``` </details>
1 parent 33cf212 commit 93533fd

File tree

1 file changed

+36
-4
lines changed

1 file changed

+36
-4
lines changed

src/components/GestureButtons.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ export interface RawButtonProps extends NativeViewGestureHandlerProps {
6565
touchSoundDisabled?: boolean;
6666
}
6767

68+
interface ButtonWithRefProps {
69+
innerRef?: React.ForwardedRef<React.ComponentType<any>>;
70+
}
71+
6872
export interface BaseButtonProps extends RawButtonProps {
6973
/**
7074
* Called when the button gets pressed (analogous to `onPress` in
@@ -94,6 +98,8 @@ export interface BaseButtonProps extends RawButtonProps {
9498
delayLongPress?: number;
9599
}
96100

101+
interface BaseButtonWithRefProps extends BaseButtonProps, ButtonWithRefProps {}
102+
97103
export interface RectButtonProps extends BaseButtonProps {
98104
/**
99105
* Background color that will be dimmed when button is in active state.
@@ -108,6 +114,8 @@ export interface RectButtonProps extends BaseButtonProps {
108114
activeOpacity?: number;
109115
}
110116

117+
interface RectButtonWithRefProps extends RectButtonProps, ButtonWithRefProps {}
118+
111119
export interface BorderlessButtonProps extends BaseButtonProps {
112120
/**
113121
* iOS only.
@@ -117,12 +125,16 @@ export interface BorderlessButtonProps extends BaseButtonProps {
117125
activeOpacity?: number;
118126
}
119127

128+
interface BorderlessButtonWithRefProps
129+
extends BorderlessButtonProps,
130+
ButtonWithRefProps {}
131+
120132
export const RawButton = createNativeWrapper(GestureHandlerButton, {
121133
shouldCancelWhenOutside: false,
122134
shouldActivateOnStart: false,
123135
});
124136

125-
export class BaseButton extends React.Component<BaseButtonProps> {
137+
class InnerBaseButton extends React.Component<BaseButtonWithRefProps> {
126138
static defaultProps = {
127139
delayLongPress: 600,
128140
};
@@ -222,6 +234,7 @@ export class BaseButton extends React.Component<BaseButtonProps> {
222234

223235
return (
224236
<RawButton
237+
ref={this.props.innerRef}
225238
rippleColor={processColor(rippleColor)}
226239
{...rest}
227240
onGestureEvent={this.onGestureEvent}
@@ -231,6 +244,11 @@ export class BaseButton extends React.Component<BaseButtonProps> {
231244
}
232245
}
233246

247+
export const BaseButton = React.forwardRef<
248+
any,
249+
Omit<BaseButtonProps, 'innerRef'>
250+
>((props, ref) => <InnerBaseButton innerRef={ref} {...props} />);
251+
234252
const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton);
235253

236254
const btnStyles = StyleSheet.create({
@@ -243,7 +261,7 @@ const btnStyles = StyleSheet.create({
243261
},
244262
});
245263

246-
export class RectButton extends React.Component<RectButtonProps> {
264+
class InnerRectButton extends React.Component<RectButtonWithRefProps> {
247265
static defaultProps = {
248266
activeOpacity: 0.105,
249267
underlayColor: 'black',
@@ -272,6 +290,7 @@ export class RectButton extends React.Component<RectButtonProps> {
272290
return (
273291
<BaseButton
274292
{...rest}
293+
ref={this.props.innerRef}
275294
style={resolvedStyle}
276295
onActiveStateChange={this.onActiveStateChange}>
277296
<Animated.View
@@ -294,7 +313,12 @@ export class RectButton extends React.Component<RectButtonProps> {
294313
}
295314
}
296315

297-
export class BorderlessButton extends React.Component<BorderlessButtonProps> {
316+
export const RectButton = React.forwardRef<
317+
any,
318+
Omit<RectButtonProps, 'innerRef'>
319+
>((props, ref) => <InnerRectButton innerRef={ref} {...props} />);
320+
321+
class InnerBorderlessButton extends React.Component<BorderlessButtonWithRefProps> {
298322
static defaultProps = {
299323
activeOpacity: 0.3,
300324
borderless: true,
@@ -316,11 +340,14 @@ export class BorderlessButton extends React.Component<BorderlessButtonProps> {
316340
};
317341

318342
render() {
319-
const { children, style, ...rest } = this.props;
343+
const { children, style, innerRef, ...rest } = this.props;
320344

321345
return (
322346
<AnimatedBaseButton
323347
{...rest}
348+
// @ts-ignore We don't want `innerRef` to be accessible from public API.
349+
// However in this case we need to set it indirectly on `BaseButton`, hence we use ts-ignore
350+
innerRef={innerRef}
324351
onActiveStateChange={this.onActiveStateChange}
325352
style={[style, Platform.OS === 'ios' && { opacity: this.opacity }]}>
326353
{children}
@@ -329,4 +356,9 @@ export class BorderlessButton extends React.Component<BorderlessButtonProps> {
329356
}
330357
}
331358

359+
export const BorderlessButton = React.forwardRef<
360+
any,
361+
Omit<BorderlessButtonProps, 'innerRef'>
362+
>((props, ref) => <InnerBorderlessButton innerRef={ref} {...props} />);
363+
332364
export { default as PureNativeButton } from './GestureHandlerButton';

0 commit comments

Comments
 (0)