Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Module Overview
Estimated Time: 3 hours | Difficulty: Intermediate | Prerequisites: Basic TypeScript knowledge
- TypeScript configuration for React Native
- Typing components, props, and state
- Navigation type safety
- API response typing
- Generic components
- Advanced TypeScript patterns
TypeScript Setup
New Project with TypeScript
# Expo (TypeScript by default)
npx create-expo-app@latest my-app
# React Native CLI with TypeScript
npx react-native@latest init MyApp --template react-native-template-typescript
Adding TypeScript to Existing Project
# Install TypeScript and types
npm install -D typescript @types/react @types/react-native
# Create tsconfig.json
npx tsc --init
TypeScript Configuration
// tsconfig.json
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
// Strict mode (recommended)
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
// Module resolution
"moduleResolution": "bundler",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
// JSX
"jsx": "react-native",
// Output
"noEmit": true,
"skipLibCheck": true,
"isolatedModules": true,
// Path aliases
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
Typing Components
Function Components
import { View, Text, StyleSheet } from 'react-native';
// Basic component with no props
function WelcomeMessage(): JSX.Element {
return (
<View style={styles.container}>
<Text>Welcome!</Text>
</View>
);
}
// Component with props
interface GreetingProps {
name: string;
age?: number; // Optional prop
}
function Greeting({ name, age }: GreetingProps): JSX.Element {
return (
<View>
<Text>Hello, {name}!</Text>
{age && <Text>You are {age} years old</Text>}
</View>
);
}
// Using React.FC (less common now, but still valid)
const GreetingFC: React.FC<GreetingProps> = ({ name, age }) => {
return (
<View>
<Text>Hello, {name}!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
},
});
Props with Children
import { View, ViewProps } from 'react-native';
import { ReactNode } from 'react';
// Method 1: Explicit children type
interface CardProps {
children: ReactNode;
title?: string;
}
function Card({ children, title }: CardProps) {
return (
<View style={styles.card}>
{title && <Text style={styles.title}>{title}</Text>}
{children}
</View>
);
}
// Method 2: PropsWithChildren utility
import { PropsWithChildren } from 'react';
interface CardBaseProps {
title?: string;
variant?: 'default' | 'outlined';
}
type CardProps = PropsWithChildren<CardBaseProps>;
function Card({ children, title, variant = 'default' }: CardProps) {
return (
<View style={[styles.card, variant === 'outlined' && styles.outlined]}>
{title && <Text>{title}</Text>}
{children}
</View>
);
}
// Method 3: Extending native component props
interface ContainerProps extends ViewProps {
centered?: boolean;
}
function Container({ centered, style, children, ...props }: ContainerProps) {
return (
<View
style={[styles.container, centered && styles.centered, style]}
{...props}
>
{children}
</View>
);
}
Event Handler Props
import { Pressable, Text, GestureResponderEvent } from 'react-native';
interface ButtonProps {
title: string;
onPress: (event: GestureResponderEvent) => void;
onLongPress?: (event: GestureResponderEvent) => void;
disabled?: boolean;
}
function Button({ title, onPress, onLongPress, disabled }: ButtonProps) {
return (
<Pressable
onPress={onPress}
onLongPress={onLongPress}
disabled={disabled}
style={({ pressed }) => [
styles.button,
pressed && styles.pressed,
disabled && styles.disabled,
]}
>
<Text style={styles.text}>{title}</Text>
</Pressable>
);
}
// Usage
<Button
title="Submit"
onPress={(e) => console.log('Pressed', e.nativeEvent)}
onLongPress={(e) => console.log('Long pressed')}
/>
Typing State
useState
import { useState } from 'react';
// Type inference (preferred when possible)
const [count, setCount] = useState(0); // number
const [name, setName] = useState(''); // string
const [isLoading, setIsLoading] = useState(false); // boolean
// Explicit typing (when needed)
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
const [data, setData] = useState<ApiResponse | undefined>(undefined);
// Complex state
interface FormState {
email: string;
password: string;
rememberMe: boolean;
}
const [form, setForm] = useState<FormState>({
email: '',
password: '',
rememberMe: false,
});
// Update partial state
setForm(prev => ({ ...prev, email: 'new@email.com' }));
useReducer
import { useReducer } from 'react';
// State type
interface CounterState {
count: number;
step: number;
}
// Action types
type CounterAction =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'setStep'; payload: number };
// Reducer function
function counterReducer(state: CounterState, action: CounterAction): CounterState {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'reset':
return { ...state, count: 0 };
case 'setStep':
return { ...state, step: action.payload };
default:
return state;
}
}
// Usage
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0, step: 1 });
return (
<View>
<Text>Count: {state.count}</Text>
<Button title="+" onPress={() => dispatch({ type: 'increment' })} />
<Button title="-" onPress={() => dispatch({ type: 'decrement' })} />
<Button title="Reset" onPress={() => dispatch({ type: 'reset' })} />
<Button
title="Set Step to 5"
onPress={() => dispatch({ type: 'setStep', payload: 5 })}
/>
</View>
);
}
Typing Refs
import { useRef, forwardRef, useImperativeHandle } from 'react';
import { View, TextInput, Animated } from 'react-native';
// Ref to native component
function SearchInput() {
const inputRef = useRef<TextInput>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<View>
<TextInput ref={inputRef} placeholder="Search..." />
<Button title="Focus" onPress={focusInput} />
</View>
);
}
// Ref to Animated.Value
function FadeView() {
const opacity = useRef(new Animated.Value(0)).current;
const fadeIn = () => {
Animated.timing(opacity, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
};
return (
<Animated.View style={{ opacity }}>
<Text>Fading content</Text>
</Animated.View>
);
}
// forwardRef with TypeScript
interface CustomInputProps {
label: string;
error?: string;
}
interface CustomInputRef {
focus: () => void;
blur: () => void;
clear: () => void;
}
const CustomInput = forwardRef<CustomInputRef, CustomInputProps>(
({ label, error }, ref) => {
const inputRef = useRef<TextInput>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
blur: () => inputRef.current?.blur(),
clear: () => inputRef.current?.clear(),
}));
return (
<View>
<Text>{label}</Text>
<TextInput ref={inputRef} />
{error && <Text style={styles.error}>{error}</Text>}
</View>
);
}
);
// Usage
function Form() {
const inputRef = useRef<CustomInputRef>(null);
return (
<View>
<CustomInput ref={inputRef} label="Email" />
<Button title="Focus" onPress={() => inputRef.current?.focus()} />
</View>
);
}
Typing Context
import { createContext, useContext, useState, ReactNode } from 'react';
// Define types
interface User {
id: string;
email: string;
name: string;
}
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
register: (email: string, password: string, name: string) => Promise<void>;
}
// Create context with default value
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// Provider component
interface AuthProviderProps {
children: ReactNode;
}
export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(false);
const login = async (email: string, password: string) => {
setIsLoading(true);
try {
// API call
const response = await authService.login(email, password);
setUser(response.user);
} finally {
setIsLoading(false);
}
};
const logout = async () => {
await authService.logout();
setUser(null);
};
const register = async (email: string, password: string, name: string) => {
setIsLoading(true);
try {
const response = await authService.register(email, password, name);
setUser(response.user);
} finally {
setIsLoading(false);
}
};
const value: AuthContextType = {
user,
isAuthenticated: !!user,
isLoading,
login,
logout,
register,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
// Custom hook with type safety
export function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
// Usage
function ProfileScreen() {
const { user, logout, isLoading } = useAuth();
if (!user) return null;
return (
<View>
<Text>Welcome, {user.name}</Text>
<Button title="Logout" onPress={logout} disabled={isLoading} />
</View>
);
}
Typing Navigation
React Navigation Types
Typing your navigation is one of the highest-value TypeScript investments in a React Native project. Without it, passing wrong params to a screen is a runtime crash that only surfaces when a tester taps the right button. With proper typing, the compiler catches it instantly. The setup below looks like a lot of boilerplate, but you write it once and it protects everynavigation.navigate() call in your entire app.
// src/shared/types/navigation.types.ts
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native';
// Root Stack
export type RootStackParamList = {
Auth: NavigatorScreenParams<AuthStackParamList>;
Main: NavigatorScreenParams<MainTabParamList>;
Modal: { title: string; content: string };
ProductDetail: { productId: string };
};
// Auth Stack
export type AuthStackParamList = {
Login: undefined;
Register: undefined;
ForgotPassword: { email?: string };
};
// Main Tabs
export type MainTabParamList = {
Home: undefined;
Search: { query?: string };
Cart: undefined;
Profile: undefined;
};
// Screen props types
export type RootStackScreenProps<T extends keyof RootStackParamList> =
NativeStackScreenProps<RootStackParamList, T>;
export type AuthStackScreenProps<T extends keyof AuthStackParamList> =
CompositeScreenProps<
NativeStackScreenProps<AuthStackParamList, T>,
RootStackScreenProps<keyof RootStackParamList>
>;
export type MainTabScreenProps<T extends keyof MainTabParamList> =
CompositeScreenProps<
BottomTabScreenProps<MainTabParamList, T>,
RootStackScreenProps<keyof RootStackParamList>
>;
// Declare global types for useNavigation
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
Using Navigation Types
// screens/LoginScreen.tsx
import { View, Text, Button } from 'react-native';
import type { AuthStackScreenProps } from '@/shared/types/navigation.types';
type Props = AuthStackScreenProps<'Login'>;
export function LoginScreen({ navigation, route }: Props) {
const handleLogin = () => {
// Navigate to main app
navigation.replace('Main', { screen: 'Home' });
};
const handleForgotPassword = () => {
// Navigate with params
navigation.navigate('ForgotPassword', { email: 'user@example.com' });
};
return (
<View>
<Text>Login Screen</Text>
<Button title="Login" onPress={handleLogin} />
<Button title="Forgot Password" onPress={handleForgotPassword} />
</View>
);
}
// screens/ProductDetailScreen.tsx
import type { RootStackScreenProps } from '@/shared/types/navigation.types';
type Props = RootStackScreenProps<'ProductDetail'>;
export function ProductDetailScreen({ navigation, route }: Props) {
const { productId } = route.params; // Type-safe params
return (
<View>
<Text>Product ID: {productId}</Text>
<Button title="Go Back" onPress={() => navigation.goBack()} />
</View>
);
}
useNavigation and useRoute Hooks
import { useNavigation, useRoute } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RouteProp } from '@react-navigation/native';
import type { RootStackParamList } from '@/shared/types/navigation.types';
// In a component
function ProductCard({ productId }: { productId: string }) {
// Type the navigation hook
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const handlePress = () => {
navigation.navigate('ProductDetail', { productId });
};
return (
<Pressable onPress={handlePress}>
<Text>View Product</Text>
</Pressable>
);
}
// Getting route params
function ProductDetailScreen() {
const route = useRoute<RouteProp<RootStackParamList, 'ProductDetail'>>();
const { productId } = route.params;
return <Text>Product: {productId}</Text>;
}
Typing API Responses
// src/shared/types/api.types.ts
// Generic API response wrapper
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
interface ApiError {
message: string;
code: string;
details?: Record<string, string[]>;
}
// Domain types
interface User {
id: string;
email: string;
name: string;
avatar?: string;
createdAt: string;
}
interface Product {
id: string;
name: string;
description: string;
price: number;
images: string[];
category: Category;
inStock: boolean;
}
interface Category {
id: string;
name: string;
slug: string;
}
// API service with types
const apiClient = {
async get<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
async post<T, D>(url: string, data: D): Promise<T> {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
};
// Usage
async function fetchProducts(): Promise<PaginatedResponse<Product>> {
return apiClient.get<PaginatedResponse<Product>>('/api/products');
}
async function createUser(data: { email: string; password: string; name: string }): Promise<ApiResponse<User>> {
return apiClient.post<ApiResponse<User>, typeof data>('/api/users', data);
}
Typing with React Query
import { useQuery, useMutation, UseQueryResult } from '@tanstack/react-query';
interface Product {
id: string;
name: string;
price: number;
}
interface CreateProductInput {
name: string;
price: number;
description: string;
}
// Typed query hook
function useProducts(): UseQueryResult<Product[], Error> {
return useQuery({
queryKey: ['products'],
queryFn: async () => {
const response = await fetch('/api/products');
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
},
});
}
// Typed mutation hook
function useCreateProduct() {
return useMutation<Product, Error, CreateProductInput>({
mutationFn: async (input) => {
const response = await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
if (!response.ok) throw new Error('Failed to create');
return response.json();
},
});
}
// Usage in component
function ProductList() {
const { data: products, isLoading, error } = useProducts();
const createProduct = useCreateProduct();
if (isLoading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return (
<View>
{products?.map((product) => (
<Text key={product.id}>{product.name}</Text>
))}
<Button
title="Add Product"
onPress={() => createProduct.mutate({ name: 'New', price: 99, description: 'Desc' })}
/>
</View>
);
}
Generic Components
import { FlatList, FlatListProps, Text, View } from 'react-native';
// Generic list component
interface GenericListProps<T> extends Omit<FlatListProps<T>, 'renderItem'> {
data: T[];
renderItem: (item: T, index: number) => JSX.Element;
keyExtractor: (item: T) => string;
emptyMessage?: string;
}
function GenericList<T>({
data,
renderItem,
keyExtractor,
emptyMessage = 'No items',
...props
}: GenericListProps<T>) {
if (data.length === 0) {
return (
<View style={styles.empty}>
<Text>{emptyMessage}</Text>
</View>
);
}
return (
<FlatList
data={data}
renderItem={({ item, index }) => renderItem(item, index)}
keyExtractor={keyExtractor}
{...props}
/>
);
}
// Usage
interface User {
id: string;
name: string;
}
function UserList() {
const users: User[] = [
{ id: '1', name: 'John' },
{ id: '2', name: 'Jane' },
];
return (
<GenericList<User>
data={users}
renderItem={(user) => <Text>{user.name}</Text>}
keyExtractor={(user) => user.id}
emptyMessage="No users found"
/>
);
}
Generic Select Component
interface SelectOption<T> {
label: string;
value: T;
}
interface SelectProps<T> {
options: SelectOption<T>[];
value: T | null;
onChange: (value: T) => void;
placeholder?: string;
}
function Select<T>({ options, value, onChange, placeholder }: SelectProps<T>) {
const selectedOption = options.find((opt) => opt.value === value);
return (
<View style={styles.select}>
<Text>{selectedOption?.label ?? placeholder ?? 'Select...'}</Text>
{options.map((option, index) => (
<Pressable
key={index}
onPress={() => onChange(option.value)}
style={styles.option}
>
<Text>{option.label}</Text>
</Pressable>
))}
</View>
);
}
// Usage with different types
type Status = 'pending' | 'active' | 'completed';
function StatusSelect() {
const [status, setStatus] = useState<Status | null>(null);
const options: SelectOption<Status>[] = [
{ label: 'Pending', value: 'pending' },
{ label: 'Active', value: 'active' },
{ label: 'Completed', value: 'completed' },
];
return (
<Select<Status>
options={options}
value={status}
onChange={setStatus}
placeholder="Select status"
/>
);
}
Utility Types
// Common utility types for React Native
// Make all properties optional
type PartialUser = Partial<User>;
// Make all properties required
type RequiredUser = Required<User>;
// Pick specific properties
type UserPreview = Pick<User, 'id' | 'name' | 'avatar'>;
// Omit specific properties
type UserWithoutPassword = Omit<User, 'password'>;
// Extract props from component
type ButtonProps = React.ComponentProps<typeof Button>;
// Extract style props
type ViewStyle = React.ComponentProps<typeof View>['style'];
// Readonly
type ReadonlyUser = Readonly<User>;
// Record type
type UserMap = Record<string, User>;
// Custom utility types
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type AsyncFunction<T> = () => Promise<T>;
// Discriminated unions
type LoadingState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function useAsyncData<T>(): LoadingState<T> {
// Implementation
}
// Usage
function DataDisplay() {
const state = useAsyncData<User[]>();
switch (state.status) {
case 'idle':
return <Text>Ready to load</Text>;
case 'loading':
return <ActivityIndicator />;
case 'success':
return <UserList users={state.data} />;
case 'error':
return <Text>Error: {state.error.message}</Text>;
}
}
Type Guards
// Type guards for runtime type checking
interface User {
type: 'user';
id: string;
name: string;
}
interface Admin {
type: 'admin';
id: string;
name: string;
permissions: string[];
}
type Account = User | Admin;
// Type guard function
function isAdmin(account: Account): account is Admin {
return account.type === 'admin';
}
// Usage
function AccountInfo({ account }: { account: Account }) {
if (isAdmin(account)) {
// TypeScript knows account is Admin here
return (
<View>
<Text>{account.name} (Admin)</Text>
<Text>Permissions: {account.permissions.join(', ')}</Text>
</View>
);
}
// TypeScript knows account is User here
return (
<View>
<Text>{account.name}</Text>
</View>
);
}
// Type guard for API responses
interface SuccessResponse<T> {
success: true;
data: T;
}
interface ErrorResponse {
success: false;
error: string;
}
type ApiResult<T> = SuccessResponse<T> | ErrorResponse;
function isSuccess<T>(result: ApiResult<T>): result is SuccessResponse<T> {
return result.success === true;
}
// Usage
async function fetchUser(id: string) {
const result = await api.get<ApiResult<User>>(`/users/${id}`);
if (isSuccess(result)) {
return result.data; // Type: User
} else {
throw new Error(result.error);
}
}
Best Practices
Enable Strict Mode
Always use
"strict": true in tsconfig.json for maximum type safetyAvoid 'any'
Use
unknown instead of any when type is truly unknownType Inference
Let TypeScript infer types when possible; explicit types when needed
Discriminated Unions
Use discriminated unions for state that can be in different shapes
Common Mistakes to Avoid
// ❌ Bad: Using 'any'
const handleData = (data: any) => { };
// ✅ Good: Use proper types or 'unknown'
const handleData = (data: unknown) => {
if (typeof data === 'string') {
// Now TypeScript knows data is string
}
};
// ❌ Bad: Non-null assertion without checking
const user = users.find(u => u.id === id)!;
// ✅ Good: Handle the undefined case
const user = users.find(u => u.id === id);
if (!user) {
throw new Error('User not found');
}
// ❌ Bad: Type assertion without validation
const data = response as User;
// ✅ Good: Validate the data
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data
);
}
if (isUser(response)) {
// Now TypeScript knows response is User
}
Platform-Specific Typing
React Native components often have props that only work on one platform. TypeScript helps you catch these mismatches, but only if you understand the type definitions.import { TextInput, Platform, TextInputProps } from 'react-native';
// Some TextInput props are iOS-only or Android-only.
// TypeScript allows all props on both platforms because the types are
// a union. The compiler will NOT warn you that textAlignVertical is
// Android-only or that clearButtonMode is iOS-only.
// You must rely on documentation and testing.
interface SearchInputProps {
value: string;
onChangeText: (text: string) => void;
}
function SearchInput({ value, onChangeText }: SearchInputProps) {
return (
<TextInput
value={value}
onChangeText={onChangeText}
placeholder="Search..."
// clearButtonMode is iOS-only -- renders a small "X" button inside the input.
// On Android, this prop is silently ignored. If you need a clear button on
// Android, you must build it yourself with a Pressable overlay.
clearButtonMode="while-editing"
// textAlignVertical is Android-only -- controls vertical text alignment in
// multiline inputs. iOS always aligns multiline text to the top by default.
textAlignVertical="top"
// Platform-specific keyboard appearance
keyboardAppearance={Platform.OS === 'ios' ? 'light' : undefined}
/>
);
}
// Typing platform-specific style values
import { ViewStyle } from 'react-native';
// iOS shadow props exist in ViewStyle but have no effect on Android.
// Android's 'elevation' exists in ViewStyle but has no effect on iOS.
// TypeScript accepts both on either platform -- runtime behavior differs.
function createShadowStyle(): ViewStyle {
return Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
},
android: {
elevation: 4,
},
default: {},
}) as ViewStyle;
}
Key insight for mobile TypeScript: React Native’s type definitions are a lowest-common-denominator union of iOS and Android capabilities. TypeScript will happily let you set
android_ripple on iOS (where it is silently ignored) or clearButtonMode on Android (where it does nothing). Type safety tells you the prop exists on the component — it does not tell you whether it works on the current platform. Always cross-reference the React Native docs for platform-specific behavior, and test on both iOS and Android simulators.Mobile TypeScript Pitfalls
TypeScript pitfalls specific to React Native:Native module types lag behind. Third-party native modules (camera, maps, Bluetooth) often have incomplete or outdated type definitions. When
@types/some-library does not exist, you face a choice: write declare module 'some-library' with minimal types, or use // @ts-ignore. Prefer the former — even a partial declaration with any return types is better than no types, because it at least documents which functions exist.Navigation params are not runtime-validated. TypeScript ensures you pass the right params at compile time, but deep links and push notification handlers bypass your navigation calls entirely. A deep link to /product/abc will populate route.params.id as a string even if your type says id: number. Always validate params from external sources at runtime, even when TypeScript says the types match.Platform.select() return type is too broad. TypeScript infers the return type as the union of all branches, which means the iOS-specific branch’s types leak into the Android code path and vice versa. If this causes issues, use explicit type annotations or as assertions on the result.Hermes does not support all ES6+ features. While TypeScript compiles to JS, the runtime engine (Hermes) has a limited feature set. Features like Proxy, Reflect.construct, and some Intl APIs are missing or incomplete. TypeScript will happily let you write code that uses these — the error only appears at runtime on the device. Check the Hermes language features table when using advanced JS features.Next Steps
Module 5: Core Components Deep Dive
Master React Native’s fundamental building blocks with TypeScript