Skip to content

Common Patterns

Frequently used code patterns.

asChild prop

Most primitive components have an asChild prop. When it is set to true, it passes all of its props to its immediate child and renders the child instead of the primitive. To make this possible, the internal implementation of the primitive uses the Slot primitive.

Examples

Implementation with an asChild prop

Using the asChild prop to conditionally render a custom Button as a Pressable or a Slot.Pressable.

~/example/button
import * as Slot from '@rn-primitives/slot'
import { Pressable, PressableProps } from 'react-native'
export function Button({ asChild, onPress: onPressProp, ...props }: PressableProps & { asChild?: boolean }) {
function onPress(ev) {
console.log('Button pressed')
onPressProp?.(ev)
}
// If asChild is true, it does not render a Pressable, instead it passes all of its props to the first child (which needs to be of type Pressable).
const Component = asChild ? Pressable : Slot.Pressable
return <Component onPress={onPress} {...props} />
}

Use of a component that has an asChild prop

Setting the asChild prop to true to pass all of the Button props to the Pressable component.

~/other/thing
import { Button } from '~/example/button'
import { Pressable, Text } from 'react-native'
function Thing(){
return (
<Button asChild>
{/* On Press, it will also log "Button pressed" since the `Button` props will be merged and passed to the `Pressable` */}
<Pressable onPress={() => {
console.log('Pressed')
}}>
<Text>Press me</Text>
</Pressable>
</Button>
)
}

Forwarding Refs

Refs are used for Direct Manipulation. A common practice is to set a ref to a component, then use the ref to call methods or access properties of the component.

Passing a ref to react-native components

Passing a ref to the TextInput component, allows us to call the focus method when the Pressable is pressed.

~/example/button
import * as React from 'react'
import { TextInput, View, Pressable, View } from 'react-native'
function Example() {
const inputRef = React.useRef<TextInput>(null)
function onPress() {
inputRef.current?.focus()
}
return (
<View>
<Pressable onPress={onPress}>
<Text>Focus</Text>
</Pressable>
<TextInput ref={inputRef} />
</View>
)
}

Custom components with ref

In React, the ref prop is a reserved prop since it needs to be handled differently. To forward a ref to a child component, you must use the React.forwardRef function.

Creating a component that accepts a ref

~/example/custom-input
import * as React from 'react'
import { TextInput, TextInputProps, View, Pressable, View } from 'react-native'
const CustomInput = React.forwardRef<TextInput, TextInputProps>((props, ref) => {
return <TextInput ref={ref} {...props} />
})
function Example() {
const inputRef = React.useRef<TextInput>(null)
function onPress() {
inputRef.current?.focus()
}
return (
<View>
<Pressable onPress={onPress}>
<Text>Focus</Text>
</Pressable>
<CustomInput ref={inputRef} />
</View>
)
}

Programmatically Showing and Hiding Components

For most components, you can use props to manipulate the visibility of it or its children components. For example, you can pass the open prop to the AlertDialog to show or hide it. However, for some components, the position of its content depends on the onPress or onLongPress events. In this case, you will need to pass a ref to the trigger component and call a method to show or hide it.

Example with content that depends on the trigger position

import * as HoverCardPrimitive from '@rn-primitives/hover-card';
import { Text, View } from 'react-native';
import * as React from 'react';
function OnMountExample() {
const triggerRef = React.useRef<React.ElementRef<typeof HoverCardPrimitive.Trigger>>(null);
React.useEffect(() => {
const timeout = setTimeout(() => {
triggerRef.current?.open();
// Wait for items to be properly layed out and wait for the animation to finish
}, 100 + 200);
return () => {
clearTimeout(timeout);
};
}, []);
return (
<HoverCardPrimitive.Root>
<HoverCardPrimitive.Trigger ref={triggerRef}>
<Text>@nextjs</Text>
</HoverCardPrimitive.Trigger>
<HoverCardPrimitive.Content>
<View>
<Text>@nextjs</Text>
<Text>
The React Framework – created and maintained by @vercel.
</Text>
<View>
<Text>
Joined December 2021
</Text>
</View>
</View>
</HoverCardPrimitive.Content>
</HoverCardPrimitive.Root>
);
}

A Portal Component inside a Presentation Modal screen

  1. Import the PortalHost component
  2. Import the FullWindowOverlay component
  3. Set a custom name for your portal and place it in a variable at the top of the file.
  4. Create a window overlay component that adapts based on the platform (iOS or others)
  5. In the component where you need a portal, pass the custom name
  6. At the bottom of your content (the fragment wrapping everything), place the WindowOverlay component. Inside it, add PortalHost as a child.
  7. Make sure the PortalHost also gets the custom portal name by passing it to the PortalHost component.
// #1
import { PortalHost } from '@rn-primitives/portal';
import * as React from 'react';
import { Platform } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
// #2
import { FullWindowOverlay } from "react-native-screens"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '~/components/ui/select';
// #3
const CUSTOM_PORTAL_HOST_NAME = 'modal-example';
// #4
const WindowOverlay = Platform.OS === "ios" ? FullWindowOverlay : React.Fragment
/**
* This screen is a Stack.Screen with a presentation of `modal`.
*/
export default function ModalScreen() {
const insets = useSafeAreaInsets();
const contentInsets = {
top: insets.top,
bottom: insets.bottom,
left: 16,
right: 16,
};
return (
<>
<View className='flex-1 justify-center items-center'>
<Select defaultValue={{ value: 'apple', label: 'Apple' }}>
<SelectTrigger>
<SelectValue
className='text-foreground text-sm native:text-lg'
placeholder='Select a fruit'
/>
</SelectTrigger>
<SelectContent
insets={contentInsets}
className='w-full'
// #5
portalHost={CUSTOM_PORTAL_HOST_NAME}
>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
<SelectItem label='Apple' value='apple'>
Apple
</SelectItem>
<SelectItem label='Banana' value='banana'>
Banana
</SelectItem>
<SelectItem label='Blueberry' value='blueberry'>
Blueberry
</SelectItem>
<SelectItem label='Grapes' value='grapes'>
Grapes
</SelectItem>
<SelectItem label='Pineapple' value='pineapple'>
Pineapple
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</View>
{/* #6 */}
<WindowOverlay>
{/* #7 */}
<PortalHost name={CUSTOM_PORTAL_HOST_NAME} />
</WindowOverlay>
</>
)
}