React Native and TypeScript in 2026: Type-Safe Architecture and Interview Questions
Build type-safe React Native apps with TypeScript, Codegen, TurboModules, and the Strict TypeScript API. Covers architecture patterns, typed navigation, toolchain requirements for 0.87, and interview questions.

React Native TypeScript type safety has matured significantly by mid-2026, with version 0.87 making the Strict TypeScript API the default and completing a major toolchain modernization. TypeScript is no longer optional glue between JavaScript and native code: it drives the entire contract from component props to TurboModule interfaces, catching mismatches at build time rather than in production crash logs.
React Native 0.87 (August 2026) makes the Strict TypeScript API the mandatory default. It also raises minimum requirements to Node.js 22.13+, Kotlin 2.0+, and Android Gradle Plugin 9. The opt-out for legacy deep imports remains available through 0.88 only.
Why TypeScript Is Now the Default for React Native Projects
Every npx react-native init invocation since version 0.76 scaffolds a TypeScript project. But the real shift happened at the native boundary. Before Codegen, developers wrote manual type assertions when crossing from JavaScript to Objective-C or Kotlin, a stringly-typed contract that broke silently at runtime. Codegen reads TypeScript spec files and generates C++, Objective-C++, and Java/Kotlin interfaces automatically. If the TypeScript spec declares a method returning number, the generated native interface enforces that constraint at compile time.
The React Native documentation covers the baseline setup, but the type-safe patterns that matter in production go further: typed navigation stacks, generic API hooks, discriminated unions for state machines, and Codegen-driven TurboModule specs.
The Strict TypeScript API: From Opt-In to Default
The Strict TypeScript API was introduced in React Native 0.80 as an opt-in feature. As of 0.87, it is the mandatory default for all new projects. Types are generated directly from React Native's source code rather than maintained manually, eliminating drift between the documented API and the actual implementation.
{
"extends": "@react-native/typescript-config",
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}With this configuration, importing anything from a subpath like react-native/Libraries/Text/Text triggers a type error. All imports must come from the root react-native package. If a library or older codebase still relies on deep imports, a temporary opt-out is available through 0.88:
{
"extends": "@react-native/typescript-config",
"compilerOptions": {
"customConditions": ["react-native", "react-native-legacy-deep-imports"]
}
}This opt-out will be removed in 0.89. Migration should happen before then.
Toolchain Requirements in 0.87
React Native 0.87 raises minimum versions across the build chain:
| Component | Minimum |
|---|---|
| Node.js | 22.13.0 |
| Kotlin | 2.0 (bundled: 2.2.0) |
| Android Gradle Plugin | 9.x |
| Android compileSdk | 34 (min), 37 (target) |
For Android, AGP 9 requires additional configuration:
# android/gradle.properties
android.builtInKotlin=false
android.newDsl=falseThese requirements apply to all projects targeting 0.87+. The New Architecture migration guide covers the broader context of these changes.
Type-Safe Navigation with React Navigation 7
React Navigation 7.x provides first-class TypeScript support. The key pattern: define a RootStackParamList type that maps every screen name to its expected params, then thread that type through navigators and screen components.
export type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
ArticleDetail: { articleId: string; source: 'feed' | 'search' };
};
export type AppTabParamList = {
Dashboard: undefined;
Explore: { category?: string };
Notifications: undefined;
};Screen components then receive typed props without manual casting:
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';
type Props = NativeStackScreenProps<RootStackParamList, 'ArticleDetail'>;
export function ArticleDetailScreen({ route, navigation }: Props) {
// route.params.articleId is string, guaranteed by the type
// route.params.source is 'feed' | 'search', no runtime check needed
const { articleId, source } = route.params;
// navigation.navigate('Profile', { userId: '123' }) is type-checked
// navigation.navigate('Profile', {}) causes a compile error: missing userId
return (
<ArticleView id={articleId} referrer={source} />
);
}This eliminates an entire class of runtime errors: navigating to a screen with wrong or missing params fails at build time.
For components that are not direct screen children, use useNavigation<NativeStackNavigationProp<RootStackParamList>>() to get the same type safety without prop drilling.
Building a Type-Safe TurboModule with Codegen
TurboModules replace the old Native Modules system. The TypeScript spec file serves as the single source of truth, and Codegen generates the native interfaces from it. If the spec and native implementation diverge, the build fails.
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getDeviceModel(): string;
getBatteryLevel(): Promise<number>;
getStorageInfo(): Promise<{
totalBytes: number;
freeBytes: number;
usedPercentage: number;
}>;
onBatteryChange(callback: (level: number) => void): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');Running npx react-native codegen generates the corresponding C++, Objective-C++, and Java interfaces. The native implementation must match every method signature exactly. For example, getStorageInfo must return an object with three numeric fields; returning a different shape causes a compile-time error on the native side.
class DeviceInfoModule(reactContext: ReactApplicationContext) :
NativeDeviceInfoSpec(reactContext) {
// Return type enforced by generated NativeDeviceInfoSpec
override fun getDeviceModel(): String {
return Build.MODEL
}
override fun getBatteryLevel(): Promise<Double> {
val bm = reactContext.getSystemService(Context.BATTERY_SERVICE)
as BatteryManager
val level = bm.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
).toDouble()
return Promise.resolve(level)
}
}This approach eliminates the ReadableMap and NSDictionary parsing that caused silent type coercion bugs in the old architecture. For a deeper look at the native module system, see the native modules interview questions.
Ready to ace your React Native interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Generic Data Fetching with Typed API Hooks
A reusable typed hook pattern avoids duplicating fetch logic across screens while preserving full type inference:
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
interface ApiResponse<T> {
data: T;
meta: { page: number; totalPages: number };
}
export function useApiQuery<T>(
key: readonly string[],
endpoint: string,
options?: Omit<UseQueryOptions<ApiResponse<T>>, 'queryKey' | 'queryFn'>
) {
return useQuery<ApiResponse<T>>({
queryKey: key,
queryFn: async () => {
const response = await fetch(`${API_BASE}${endpoint}`);
if (!response.ok) throw new ApiError(response.status);
return response.json() as Promise<ApiResponse<T>>;
},
...options,
});
}
// Usage, T is inferred as Article[]
interface Article {
id: string;
title: string;
publishedAt: string;
}
const { data, isLoading } = useApiQuery<Article[]>(
['articles', 'latest'],
'/articles?sort=latest'
);
// data.data is Article[], fully typed
// data.meta.totalPages is numberThe generic parameter T flows through the entire chain: from the hook call site, through the query function, to the component that consumes the result. No as casts, no any types.
Discriminated Unions for State Machines
Complex screen states (loading, error, empty, loaded) are best modeled as discriminated unions rather than a bag of optional fields:
type ScreenState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'error'; error: string; retryCount: number }
| { status: 'empty'; message: string }
| { status: 'loaded'; data: T; refreshedAt: Date };
// components/DataScreen.tsx
function renderContent<T>(state: ScreenState<T>, renderItem: (data: T) => ReactNode) {
switch (state.status) {
case 'idle':
return null;
case 'loading':
return <LoadingSpinner />;
case 'error':
// state.error is string here, TypeScript narrows automatically
return <ErrorBanner message={state.error} retries={state.retryCount} />;
case 'empty':
return <EmptyState message={state.message} />;
case 'loaded':
// state.data is T, fully typed
return renderItem(state.data);
}
}This pattern makes impossible states unrepresentable. A loading state cannot accidentally carry stale data, and an error state always includes context for debugging.
A common anti-pattern: { isLoading: boolean; error?: string; data?: T }. This allows states like { isLoading: true, error: 'fail', data: [...] }, three contradictory signals at once. Discriminated unions prevent this at the type level.
React Native TypeScript Interview Questions
These questions reflect what senior mobile engineering teams ask in 2026 interviews, now that the New Architecture and TypeScript are standard.
How does Codegen enforce type safety across the JavaScript-native boundary?
Codegen reads TypeScript (or Flow) spec files and generates C++, Objective-C++, and Java/Kotlin interface code. The generated native interfaces enforce the exact method signatures, parameter types, and return types defined in the spec. If the native implementation deviates (returning Int where the spec declares Double, or omitting a field from a struct) the native compiler rejects the build. This moves type errors from runtime crashes to build-time failures.
What is the Strict TypeScript API and why does it matter?
The Strict TypeScript API generates types directly from React Native's source code rather than maintaining hand-written .d.ts files. In 0.87, it became the mandatory default. It restricts imports to the root react-native package, deprecating deep imports. This defines a stable public API surface: internal refactors cannot break consumer code if consumers only use strict types. A temporary opt-out via customConditions remains available through 0.88.
How do you type React Navigation params across nested navigators?
Define a ParamList type per navigator and compose them using NavigatorScreenParams. For a tab navigator nested inside a stack, the stack's param list references the tab's: type RootStack = { Main: NavigatorScreenParams<TabParamList>; Modal: { id: string } }. Every navigate() call is then type-checked through the full nesting hierarchy, catching wrong screen names or missing params at compile time.
What problem do discriminated unions solve in React Native state management?
Discriminated unions model mutually exclusive states (loading, error, loaded) as separate branches of a union type, keyed by a status field. TypeScript narrows the type in each branch of a switch statement, so accessing state.data is only possible when state.status === 'loaded'. This prevents impossible states like a loading indicator showing alongside error data, a class of bugs that optional fields and boolean flags cannot prevent.
Explain the difference between TurboModules and the old Native Modules system.
Native Modules communicated over the async bridge, serializing all data to JSON. TurboModules use JSI (JavaScript Interface) for synchronous, direct C++ calls with no serialization overhead. They also load lazily (on first use instead of app startup, reducing cold start time) and use Codegen to generate type-safe interfaces from TypeScript specs. The old system relied on ReadableMap / NSDictionary parsing with runtime type coercion; TurboModules enforce types at compile time.
What APIs were removed in React Native 0.87?
0.87 removed InteractionManager (use requestIdleCallback), the NativeMethods / NativeMethodsMixin types (use HostInstance), and several StatusBar props. Deep imports to src/private/ paths are now type errors. The useTurboModules flag was also removed since TurboModules are now always enabled.
For more React Native interview questions, the complete guide covers architecture, performance, and debugging topics.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Sources
- React Native 0.87 Release Notes - Strict TypeScript API default, toolchain requirements, API removals
- React Native TypeScript Documentation - Official TypeScript setup guide
- React Navigation TypeScript Guide - Typed navigation patterns
- TanStack Query Documentation - Data fetching with type inference
What to Remember About React Native TypeScript in 2026
- The Strict TypeScript API (mandatory in 0.87) restricts imports to the stable public surface, preventing breakage from internal changes. The opt-out for legacy deep imports is available through 0.88 only.
- Codegen generates native interfaces from TypeScript spec files, moving type errors from runtime crashes to build-time failures across the JS-native boundary
- Toolchain minimums in 0.87: Node.js 22.13+, Kotlin 2.0+, AGP 9, compileSdk 34+
- Typed navigation params via
RootStackParamListandNativeStackScreenPropscatch wrong screen names and missing params before the app runs - Discriminated unions model screen states as mutually exclusive branches, making impossible states unrepresentable at the type level
- TurboModules with typed specs replace the old
ReadableMap/NSDictionaryparsing, enforcing full type safety from JavaScript through C++ to platform-native code - Generic API hooks with TanStack Query preserve type inference from endpoint to component without manual casts
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Can you spot the bug in React Native?
One real snippet, one hidden bug, one attempt a day. No account needed to try.

Written by
Anthony Fillion-MailletFounder of SharpSkill
Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.
Updated on August 25, 2026
Tags
Share
Related articles

React Native App Development in 2026: Complete Guide and Interview Questions
Master React Native app development in 2026 with the New Architecture, Hermes V1, and Fabric. Includes practical code examples and common interview questions.

React Native New Architecture in 2026: Hermes V1, Bridgeless Mode and Interview Questions
React Native New Architecture is now the default in 2026 with Hermes V1, Bridgeless Mode, TurboModules and Fabric. Deep dive into performance gains, migration patterns, and key interview questions.

React Native App Development Guide 2026: Building Production Apps and Interview Questions
Complete guide to React Native production development in 2026 covering Expo SDK 56, EAS Build, Hermes V1, the New Architecture, and interview preparation for React Native developers.