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
.
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.Pressablereturn <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.
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.
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
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
- Import the
PortalHost
component - Import the
FullWindowOverlay
component - Set a custom name for your portal and place it in a variable at the top of the file.
- Create a window overlay component that adapts based on the platform (iOS or others)
- In the component where you need a portal, pass the custom name
- At the bottom of your content (the fragment wrapping everything), place the
WindowOverlay
component. Inside it, addPortalHost
as a child. - Make sure the
PortalHost
also gets the custom portal name by passing it to thePortalHost
component.
// #1import { PortalHost } from '@rn-primitives/portal';import * as React from 'react';import { Platform } from 'react-native';import { useSafeAreaInsets } from 'react-native-safe-area-context';// #2import { FullWindowOverlay } from "react-native-screens"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue,} from '~/components/ui/select';
// #3const CUSTOM_PORTAL_HOST_NAME = 'modal-example';
// #4const 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> </> )}