Generics, discriminated unions, and reusable prop contracts turn React Native components into self-documenting APIs. Strong prop types catch invalid combinations at compile time - before they surface as layout bugs on iOS and Android.
When to reach for this: You are building shared UI primitives, list rows, or variant-driven components and want TypeScript to reject impossible prop combinations before runtime.
import type { ComponentProps, ReactElement, ReactNode } from "react";import { Pressable, Text, type TextProps, type ViewProps } from "react-native";// Inherit primitive props, override only what you owntype CardProps = ViewProps & { title: string; footer?: ReactNode;};// Extract onPress from Pressable without importing the value type manuallytype IconButtonProps = Pick<ComponentProps<typeof Pressable>, "onPress" | "disabled"> & { label: string; icon: ReactElement;};// Strict children - only Text nodes allowed inside a typography wrappertype TypographyProps = TextProps & { children: string | ReactElement<typeof Text>;};
Optional everything on variant props - { variant?: "a" \| "b"; a?: string; b?: number } lets callers pass { variant: "a", b: 1 } without error. Fix: Use a discriminated union with required fields per branch.
Duplicating onPress types - Hand-writing (event: GestureResponderEvent) => void drifts from RN updates. Fix: Use PressableProps["onPress"] or ComponentProps<typeof Pressable>["onPress"].
Casting generic list data - data as Place[] inside a shared list hides caller mistakes. Fix: Push the generic to the list component: FlatList<Place>, SectionList<Place>.
Spreading unknown props onto View - {...rest} from a wide type can pass invalid keys to native views. Fix:Omit known keys and type rest as ViewProps or a narrow pick.
Inline object types on every component - ({ name }: { name: string }) is fine once; repeated shapes belong in a named type. Fix: Export ProfileCardProps from the same file as the component.
Forgetting readonly on arrays - Props typed as T[] allow callers to .push() into passed arrays. Fix: Prefer readonly T[] for display-only data props.
Using React.FC for new code - Implicit children and legacy quirks add noise in React 19. Fix: Type the props parameter directly on a named function.
Should I use interface or type for component props?
Either works. Use interface when you expect declaration merging or extension across files; use type for unions, mapped types, and Omit/Pick compositions. Consistency within a design system matters more than the keyword.
How do I type children in React 19 function components?
Add children?: React.ReactNode to your props type when the component renders a slot. Do not rely on React.FC - it is no longer the recommended default. For stricter APIs, narrow to ReactElement or a specific component type.
What is the correct type for style props?
Use StyleProp<ViewStyle> on View, StyleProp<TextStyle> on Text, and StyleProp<ImageStyle> on Image. These accept a style object, arrays, and conditional falsy entries (condition && styles.x).
How do discriminated unions narrow inside JSX?
Narrow in the function body with if (props.variant === "error") before returning JSX. TypeScript carries the narrowed type into that branch, so props.onRetry is only required when variant is "error".
How do I share props between two similar components?
Extract a base type (RowBaseProps) and intersect with variant-specific types. Use Pick and Omit to reuse slices from primitives: Pick<PressableProps, "onPress" | "disabled">.
Export when other modules wrap or extend the component - navigation screens, Storybook stories, and test helpers benefit from export type ListRowProps. Keep internal helper props unexported.
How do I type optional callback props?
Use optional fields: onPress?: () => void. When the callback is required only for one union branch, put it inside that branch of a discriminated union instead of marking it optional globally.
Gotcha: Why does spreading props break autocomplete?
{...rest} where rest is typed too widely (e.g. Record<string, unknown>) loses key checking. Type rest as Omit<CardProps, "title"> or ViewProps so only valid native keys spread through.
How do I type icon name unions?
Use string literal unions ("star" | "pin") or as const maps:
Literal unions catch typos at compile time and drive switch exhaustiveness.
Can I use default parameter values with typed props?
Yes - function Counter({ initial = 0 }: { initial?: number }) is idiomatic. Defaults apply at runtime; TypeScript still treats the prop as optional on the caller side.
How do I type render-prop children?
type MeasureProps = { children: (size: { width: number; height: number }) => React.ReactNode;};
The callback return must be ReactNode; use ReactElement only when you require a single element.
How does strict null checking affect props?
With strict: true, optional props are T | undefined. Use prop ?? fallback or explicit guards. Avoid prop! non-null assertions in components - callers may omit the field.
Should list row components use memo with typed props?
Yes, when rows are expensive and receive stable props. Pair memo with a named props type so comparators and tests stay clear. Unstable inline onPress={() => …} lambdas defeat memoization.
How do I extend third-party component props?
Use ComponentProps<typeof ThirdPartyButton> or the package's exported props type. Re-export a narrowed version with Omit if you hide some upstream props from your design system.