ForwardRef Guide
- forwardRef: A utility function from React that allows a component to receive a
reffrom a parent and pass it down to a child component further down the render tree.
import { forwardRef } from 'react';import { StyleSheet, Text, TouchableOpacity, TouchableOpacityProps, View } from 'react-native';
type ButtonProps = { title?: string;} & TouchableOpacityProps;
export const Button = forwardRef<View, ButtonProps>(({ title, ...touchableProps }, ref) => { return ( <TouchableOpacity ref={ref} {...touchableProps} style={[styles.button, touchableProps.style]}> <Text style={styles.buttonText}>{title}</Text> </TouchableOpacity> );});
---// Use of Button <Button onPress={() => navigation.navigate('Details', { name: 'Dan',})} title="Show Details" />Line-by-Line Breakdown
Section titled “Line-by-Line Breakdown”type ButtonProps = { title?: string;} & TouchableOpacityProps;- Purpose: Defines the TypeScript interface (type) for the component’s props.
title?: string;: A prop specific to our customButton. It’s optional (?) and will be the text displayed inside the button.& TouchableOpacityProps: This uses TypeScript’s intersection type to combine our customtitleprop with all the props that a standardTouchableOpacityaccepts (likeonPress,disabled,style,accessibilityLabel, etc.). This is a powerful pattern that makes our custom component highly extensible.
export const Button = forwardRef<View, ButtonProps>(({ title, ...touchableProps }, ref) => {- Purpose: Creates and exports the Button component using
forwardRef. forwardRef<View, ButtonProps>(...): This is the function call. It takes two generic type parameters:<View>: The type of the element therefwill point to. Here, it’sViewbecauseTouchableOpacityis a wrapper that ultimately renders a native view. This means the parent’srefwill have access to all methods of aViewcomponent (e.g.,.measure()).<ButtonProps>: The type of the props the component expects.
({ title, ...touchableProps }, ref) => {: This is the render function passed toforwardRef.- It receives two arguments:
props: Destructured intotitle(our custom prop) and...touchableProps(all other props, which are theTouchableOpacityProps).ref: Therefpassed down from the parent component. This argument is only present because we wrapped the component inforwardRef.
- It receives two arguments:
return ( <TouchableOpacity ref={ref} {...touchableProps} style={[styles.button, touchableProps.style]}> <Text style={styles.buttonText}>{title}</Text> </TouchableOpacity> );});- Purpose: Renders the JSX for the component.
<TouchableOpacity ref={ref} {...touchableProps} style={[styles.button, touchableProps.style]}>ref={ref}: This is the most crucial line. It takes therefwe received from the parent and attaches it directly to theTouchableOpacitycomponent. This connects the parent’srefto the actual native element.{...touchableProps}: Spreads all the remaining props (likeonPress,disabled) onto theTouchableOpacity. This is what gives the component its extensibility.style={[styles.button, touchableProps.style]}: Merges the base styles fromstyles.buttonwith anystyleprop passed in from the parent. The parent’sstylewill override the base styles if there are conflicts, which is the expected behavior.
const styles = StyleSheet.create({ button: { // ... base button styles (e.g., padding, backgroundColor, borderRadius) }, buttonText: { // ... base text styles (e.g., color, fontSize, fontWeight) },});- Purpose: Defines the base styles for the component using React Native’s
StyleSheet.
Significance of forwardRef in this Context
Section titled “Significance of forwardRef in this Context”Using forwardRef is essential here for several reasons:
-
Direct Access: It allows parent components to directly access the underlying
TouchableOpacity(and hence the native view) instance. Without it, arefpassed to<Button ref={myRef} />would be attached to theButtonfunction component itself, which doesn’t hold a native element and is therefore useless for mostrefoperations. -
Common Use Cases with
ref:- Focus Management: A parent component (e.g., a form) might want to call
.focus()on the next input. If yourButtonwas focusable, you’d need arefto control it. - Animations: Starting an animation on the button from a parent (e.g., a shake animation for an invalid submit). This often requires a
refto the native element to use with Animated API. - Measuring Layout: Programmatically getting the button’s position or dimensions on screen using
ref.measure()for tasks like rendering a tooltip nearby or scrolling to its position. - Integrating with Native Libraries: Some third-party libraries might require a
refto a native component to function properly.
- Focus Management: A parent component (e.g., a form) might want to call
-
Reusability and Predictability: It makes the custom
Buttoncomponent behave like a built-in component. Developers expect to be able to set arefon a button, andforwardRefensures this expectation is met.
Alternative Ways to Achieve the Same Functionality
Section titled “Alternative Ways to Achieve the Same Functionality”Yes, there are alternatives, but they are generally considered inferior for this use case.
-
Using a Different Prop Name (e.g.,
innerRef):type ButtonProps = {title?: string;innerRef?: React.Ref<View>;} & TouchableOpacityProps;export const Button = ({ title, innerRef, ...touchableProps }: ButtonProps) => {return (<TouchableOpacity ref={innerRef} {...touchableProps} style={[styles.button, touchableProps.style]}><Text style={styles.buttonText}>{title}</Text></TouchableOpacity>);};// Usage: <Button title="Press" innerRef={myRef} />- Drawback: This breaks the standard React convention. Developers expect to use the prop named
ref. This makes the API less intuitive and inconsistent with the rest of the React ecosystem.
- Drawback: This breaks the standard React convention. Developers expect to use the prop named
-
Not Supporting
refat All:- You simply don’t accept a
refand don’t forward it. - Drawback: This severely limits the component’s functionality for the advanced use cases mentioned above (animations, measuring, etc.). The component is less reusable and powerful.
- You simply don’t accept a
-
Using a Class Component (Legacy):
- Before
forwardRefwas introduced in React 16.3, you could only attach arefto a class component. While this would work, it’s an outdated pattern. Functional components with hooks andforwardRefare the modern, recommended way to build React components.
- Before
Conclusion
Section titled “Conclusion”The use of forwardRef in this custom Button component is the correct and idiomatic React pattern. It provides a clean, standard API for consumers of the component while enabling powerful imperative functionalities. The alternative approaches are workarounds that introduce API inconsistencies or reduce functionality. forwardRef is the best way to handle this requirement.