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, and interview questions for 2026.

React Native TypeScript type safety has matured significantly by mid-2026, with version 0.86 shipping React 19.1, the Strict TypeScript API, and a fully bridgeless architecture powered by Codegen. 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.80 introduced the opt-in Strict TypeScript API, 0.82 permanently removed the legacy bridge, and 0.85 dropped the last interop layer. As of 0.86 (June 2026), every new project starts fully bridgeless with TypeScript types generated directly from source code.
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.
Setting Up the Strict TypeScript API
The Strict TypeScript API, introduced in React Native 0.80, restricts the public API surface to types generated directly from source code. This prevents accidental reliance on internal modules that could break between minor versions.
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"moduleResolution": "bundler",
"jsx": "react-jsx",
"types": ["react-native/types/strict"]
},
"extends": "@react-native/typescript-config/tsconfig.json"
}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, aligning with the deep-import deprecation enforced since 0.80.
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' }) — type-checked
// navigation.navigate('Profile', {}) — 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 aren't 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 — 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.
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?
Introduced in React Native 0.80, the Strict TypeScript API generates types directly from React Native's source code rather than maintaining hand-written .d.ts files. 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. Enable it via "types": ["react-native/types/strict"] in tsconfig.json.
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 — 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.
For more React Native interview questions, the native modules deep-dive covers JSI, Fabric, and bridgeless architecture in detail.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Conclusion
- The Strict TypeScript API (0.80+) restricts imports to the stable public surface, preventing breakage from internal changes — enable it in every new project
- Codegen generates native interfaces from TypeScript spec files, moving type errors from runtime crashes to build-time failures across the JS-native boundary
- 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.
Tags
Share
Related articles

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 0.85 in 2026: New Animation Backend, Strict TypeScript API and Interview Questions
Deep dive into React Native 0.85's Shared Animation Backend, post-bridge architecture, Metro TLS support, and key interview questions about the new architecture in 2026.

Expo Router in React Native: File-Based Navigation Complete Guide
Master Expo Router for React Native with this complete tutorial covering file-based routing, layouts, dynamic routes, typed navigation, and advanced patterns like modals and tabs in 2026.