TypeScript in RN Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
Extend expo/tsconfig.base: Expo SDK 57 projects should start from the official base config - it includes the right jsx, module resolution, and paths for Metro and native module typings.
Turn on strict for greenfield screens: New .tsx files should compile under full strictness from day one; brownfield apps phase flags in module by module instead of flipping everything at once.
Name every component's props: Export a ComponentNameProps interface (or type) beside the component - inline object types do not scale across refactors and Storybook stories.
Prefer discriminated unions for variants: Model { variant: "a"; aField: string } | { variant: "b"; bField: number } instead of optional fields on every branch - TypeScript narrows correctly inside each case.
Avoid React.FC in new code: Type the function parameter directly - React.FC adds implicit children noise and is deprecated idiomatically in React 19 projects.
Reuse RN prop types with ComponentProps: Extend primitives via ComponentProps<typeof Pressable> and Omit only the keys you replace - hand-rolled prop lists drift when RN adds accessibility or platform props.
Type press handlers from PressableProps: Use PressableProps["onPress"] instead of hand-writing GestureResponderEvent signatures that may miss optional parameters.
Push generics to list components: Declare FlatList<Place> and SectionList<Place> at the list boundary - casting data as Place[] inside shared lists hides caller mistakes.
Use readonly T[] for display props: Prevent callers from mutating arrays you render - props are a contract, not a mutable shared buffer.
Enable Expo Router typed routes: Set experiments.typedRoutes: true in app.config and include .expo/types/**/*.ts so Href and router.push catch invalid paths at compile time.
Regenerate route types in CI: Run npx expo customize tsconfig.json before tsc --noEmit - without generated types, every href degrades to string.
Pass dynamic routes as object hrefs: Use { pathname: "/user/[id]", params: { id } } - string literals like "/user/[id]" fail typed-route checks because params are missing.
Match bracket names exactly: A file [id].tsx requires params: { id }, not { userId } - param keys must mirror the filesystem segment.
Validate URL params with Zod: Typed routes prove path shape, not value safety - parse useLocalSearchParams output before using IDs in fetch calls.
Treat fetch JSON as unknown: res.json() does not validate - assign to unknown, then Schema.parse(data); never as MyInterface at the boundary.
Model API envelopes explicitly: Parse { items, nextCursor } wrappers, not bare arrays - backends add pagination fields without a major version bump.
Use .nullable() for SQL nulls: Backend avatarUrl: null fails .optional() schemas - distinguish absent keys (optional) from present nulls (nullable).
Coerce numbers at the boundary: Gateways sometimes stringify numeric fields - z.coerce.number() belongs in the Zod schema, not repeated Number() calls in screens.
Type styles as StyleProp<ViewStyle>: Callers need to pass style arrays and falsy guards - bare ViewStyle rejects [styles.base, pressed && styles.active].
Use PressableProps["style"] for pressable wrappers: Pressable styles can be functions of pressed state - narrowing to ViewStyle breaks the callback form.
Wrap native modules with typed requireNativeModule: Declare a NativeModule interface and parse async returns with Zod - JSI bindings can still return unexpected shapes across OS versions.
Use requireOptionalNativeModule in Expo Go paths: Modules missing from Expo Go should degrade gracefully - optional require plus feature flags beats try/catch around every call.
Phase strictness in brownfield apps: Enable noImplicitAny, then strictNullChecks, then strict - one flag per sprint keeps PRs reviewable.
Prefer @ts-expect-error over @ts-ignore: Expect-error fails when the underlying mistake is fixed - ignore comments linger and hide real regressions.
Typecheck in CI separately from lint: Run tsc --noEmit on every PR - ESLint does not catch invalid route hrefs, Zod mismatches, or broken generic inference.
style?: ViewStyle prop with style?: StyleProp<ViewStyle> in shared components.ComponentProps<typeof Pressable>, omit children if you rename it, and spread ...pressableProps last only when callers should override defaults.expo customize tsconfig.json.strictNullChecks - it surfaces the most real bugs but also the most legacy noise.noImplicitAny on new modules first.Href types and CI setupStack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 19, 2026