Documentation Index Fetch the complete documentation index at: https://mintlify.com/facebook/react-native/llms.txt
Use this file to discover all available pages before exploring further.
TypeScript adds static typing to JavaScript, helping catch errors at compile time and improving code quality. React Native has first-class TypeScript support.
Why TypeScript?
Type safety : Catch errors before runtime
Better IDE support : Autocomplete, refactoring, navigation
Self-documenting : Types serve as inline documentation
Easier refactoring : Rename and restructure with confidence
Better tooling : Enhanced debugging and testing
Setting Up TypeScript
New Projects
React Native CLI creates TypeScript projects by default:
npx react-native@latest init MyApp
Existing JavaScript Projects
Add TypeScript to an existing project:
npm install --save-dev typescript @types/react @types/react-native
Create tsconfig.json:
{
"compilerOptions" : {
"target" : "esnext" ,
"module" : "commonjs" ,
"lib" : [ "es2019" ],
"allowJs" : true ,
"jsx" : "react-native" ,
"noEmit" : true ,
"isolatedModules" : true ,
"strict" : true ,
"moduleResolution" : "node" ,
"allowSyntheticDefaultImports" : true ,
"esModuleInterop" : true ,
"skipLibCheck" : true ,
"resolveJsonModule" : true
},
"exclude" : [
"node_modules" ,
"babel.config.js" ,
"metro.config.js" ,
"jest.config.js"
]
}
Rename files:
# Rename .js to .tsx for components
mv App.js App.tsx
# Rename .js to .ts for utilities
mv utils.js utils.ts
Type Definitions
React Native Types
React Native includes built-in TypeScript definitions:
import { View , Text , StyleSheet , ViewStyle , TextStyle } from 'react-native' ;
interface Styles {
container : ViewStyle ;
title : TextStyle ;
}
const styles = StyleSheet . create < Styles >({
container: {
flex: 1 ,
padding: 16 ,
},
title: {
fontSize: 24 ,
fontWeight: 'bold' ,
},
});
Component Props
import React from 'react' ;
import { View , Text , TouchableOpacity } from 'react-native' ;
interface ButtonProps {
title : string ;
onPress : () => void ;
disabled ?: boolean ;
variant ?: 'primary' | 'secondary' ;
}
function Button ({
title ,
onPress ,
disabled = false ,
variant = 'primary' ,
} : ButtonProps ) {
return (
< TouchableOpacity
onPress = { onPress }
disabled = { disabled }
style = { [styles.button, styles[variant]]}
>
<Text style={styles.text}>{title}</Text>
</TouchableOpacity>
);
}
export default Button;
Children Props
import React , { ReactNode } from 'react' ;
import { View } from 'react-native' ;
interface CardProps {
children : ReactNode ;
padding ?: number ;
}
function Card ({ children , padding = 16 } : CardProps ) {
return (
< View style = {{ padding }} >
{ children }
</ View >
);
}
Hooks with TypeScript
useState
import { useState } from 'react' ;
interface User {
id : number ;
name : string ;
email : string ;
}
function UserProfile () {
// Type inference
const [ count , setCount ] = useState ( 0 );
// Explicit type
const [ user , setUser ] = useState < User | null >( null );
// Array type
const [ items , setItems ] = useState < string []>([]);
return (
< View >
< Text > Count : { count }</ Text >
{ user && < Text >{ user . name }</ Text >}
</ View >
);
}
useEffect
import { useEffect } from 'react' ;
function Timer () {
const [ seconds , setSeconds ] = useState < number >( 0 );
useEffect (() => {
const interval = setInterval (() => {
setSeconds ( s => s + 1 );
}, 1000 );
// Cleanup function is typed automatically
return () => clearInterval ( interval );
}, []); // Dependencies array is type-checked
return < Text >{ seconds } s </ Text > ;
}
useRef
import { useRef } from 'react' ;
import { TextInput } from 'react-native' ;
function Form () {
// Ref for React Native component
const inputRef = useRef < TextInput >( null );
// Ref for mutable value
const countRef = useRef < number >( 0 );
const focusInput = () => {
inputRef . current ?. focus ();
};
return (
< TextInput
ref = { inputRef }
placeholder = "Enter text"
/>
);
}
Custom Hooks
import { useState , useEffect } from 'react' ;
interface FetchState < T > {
data : T | null ;
loading : boolean ;
error : Error | null ;
}
function useFetch < T >( url : string ) : FetchState < T > {
const [ state , setState ] = useState < FetchState < T >>({
data: null ,
loading: true ,
error: null ,
});
useEffect (() => {
fetch ( url )
. then ( res => res . json ())
. then ( data => setState ({ data , loading: false , error: null }))
. catch ( error => setState ({ data: null , loading: false , error }));
}, [ url ]);
return state ;
}
// Usage
interface User {
id : number ;
name : string ;
}
function UserList () {
const { data , loading , error } = useFetch < User []>( '/api/users' );
if ( loading ) return < ActivityIndicator />;
if ( error ) return < Text > Error : {error. message } </ Text > ;
return (
< View >
{ data ?. map ( user => (
< Text key = {user. id } > {user. name } </ Text >
))}
</ View >
);
}
Navigation Types
React Navigation
import { NavigatorScreenParams } from '@react-navigation/native' ;
import { NativeStackScreenProps } from '@react-navigation/native-stack' ;
// Define navigation structure
type RootStackParamList = {
Home : undefined ;
Profile : { userId : string };
Settings : { theme ?: 'light' | 'dark' };
};
// Screen component props
type HomeScreenProps = NativeStackScreenProps < RootStackParamList , 'Home' >;
function HomeScreen ({ navigation , route } : HomeScreenProps ) {
const navigateToProfile = () => {
navigation . navigate ( 'Profile' , { userId: '123' });
};
return (
< View >
< Button title = "View Profile" onPress = { navigateToProfile } />
</ View >
);
}
// Type-safe navigation hook
import { useNavigation } from '@react-navigation/native' ;
import { NativeStackNavigationProp } from '@react-navigation/native-stack' ;
type HomeScreenNavigationProp = NativeStackNavigationProp <
RootStackParamList ,
'Home'
>;
function useHomeNavigation () {
return useNavigation < HomeScreenNavigationProp >();
}
Style Types
Typed StyleSheet
import { StyleSheet , ViewStyle , TextStyle , ImageStyle } from 'react-native' ;
type Styles = {
container : ViewStyle ;
title : TextStyle ;
image : ImageStyle ;
};
const styles = StyleSheet . create < Styles >({
container: {
flex: 1 ,
padding: 16 ,
backgroundColor: '#fff' ,
},
title: {
fontSize: 24 ,
fontWeight: 'bold' ,
color: '#333' ,
},
image: {
width: 100 ,
height: 100 ,
resizeMode: 'cover' ,
},
});
Dynamic Styles
import { ViewStyle } from 'react-native' ;
interface ThemeColors {
primary : string ;
secondary : string ;
background : string ;
}
function createStyles ( theme : ThemeColors ) {
return StyleSheet . create ({
container: {
backgroundColor: theme . background ,
} as ViewStyle ,
button: {
backgroundColor: theme . primary ,
} as ViewStyle ,
});
}
API Types
Fetch with Types
interface User {
id : number ;
name : string ;
email : string ;
}
interface ApiResponse < T > {
data : T ;
status : number ;
message : string ;
}
async function fetchUser ( id : number ) : Promise < User > {
const response = await fetch ( `/api/users/ ${ id } ` );
const json : ApiResponse < User > = await response . json ();
return json . data ;
}
// Usage
useEffect (() => {
fetchUser ( 123 ). then ( user => {
console . log ( user . name ); // Type-safe access
});
}, []);
AsyncStorage
import AsyncStorage from '@react-native-async-storage/async-storage' ;
interface StorageKeys {
USER_TOKEN : string ;
USER_PREFERENCES : UserPreferences ;
THEME : 'light' | 'dark' ;
}
interface UserPreferences {
notifications : boolean ;
language : string ;
}
class TypedStorage {
static async getItem < K extends keyof StorageKeys >(
key : K ,
) : Promise < StorageKeys [ K ] | null > {
const value = await AsyncStorage . getItem ( key );
return value ? JSON . parse ( value ) : null ;
}
static async setItem < K extends keyof StorageKeys >(
key : K ,
value : StorageKeys [ K ],
) : Promise < void > {
await AsyncStorage . setItem ( key , JSON . stringify ( value ));
}
}
// Usage - fully typed!
const prefs = await TypedStorage . getItem ( 'USER_PREFERENCES' );
if ( prefs ) {
console . log ( prefs . notifications ); // Type-safe
}
Event Handlers
Touch Events
import { GestureResponderEvent } from 'react-native' ;
interface Props {
onPress : ( event : GestureResponderEvent ) => void ;
}
function CustomButton ({ onPress } : Props ) {
const handlePress = ( event : GestureResponderEvent ) => {
console . log ( 'Pressed at' , event . nativeEvent . pageX , event . nativeEvent . pageY );
onPress ( event );
};
return (
< TouchableOpacity onPress = { handlePress } >
< Text > Press Me </ Text >
</ TouchableOpacity >
);
}
Text Input Events
import { NativeSyntheticEvent , TextInputChangeEventData } from 'react-native' ;
function SearchInput () {
const [ query , setQuery ] = useState < string >( '' );
const handleChange = (
event : NativeSyntheticEvent < TextInputChangeEventData >,
) => {
const text = event . nativeEvent . text ;
setQuery ( text );
};
return (
< TextInput
value = { query }
onChange = { handleChange }
placeholder = "Search..."
/>
);
}
Context with TypeScript
import React , { createContext , useContext , useState , ReactNode } from 'react' ;
interface User {
id : number ;
name : string ;
email : string ;
}
interface AuthContextType {
user : User | null ;
login : ( user : User ) => void ;
logout : () => void ;
isAuthenticated : boolean ;
}
const AuthContext = createContext < AuthContextType | undefined >( undefined );
interface AuthProviderProps {
children : ReactNode ;
}
export function AuthProvider ({ children } : AuthProviderProps ) {
const [ user , setUser ] = useState < User | null >( null );
const login = ( user : User ) => setUser ( user );
const logout = () => setUser ( null );
const isAuthenticated = user !== null ;
return (
< AuthContext . Provider value = {{ user , login , logout , isAuthenticated }} >
{ children }
</ AuthContext . Provider >
);
}
export function useAuth () : AuthContextType {
const context = useContext ( AuthContext );
if ( ! context ) {
throw new Error ( 'useAuth must be used within AuthProvider' );
}
return context ;
}
// Usage
function ProfileScreen () {
const { user , isAuthenticated , logout } = useAuth ();
if ( ! isAuthenticated ) {
return < Text > Please log in </ Text > ;
}
return (
< View >
< Text > Welcome , { user . name } !</ Text >
< Button title = "Logout" onPress = { logout } />
</ View >
);
}
Advanced TypeScript Patterns
Generic Components
interface ListProps < T > {
data : T [];
renderItem : ( item : T ) => ReactNode ;
keyExtractor : ( item : T ) => string ;
}
function List < T >({ data , renderItem , keyExtractor } : ListProps < T >) {
return (
< View >
{ data . map ( item => (
< View key = { keyExtractor ( item )} >
{ renderItem ( item )}
</ View >
))}
</ View >
);
}
// Usage
interface User {
id : number ;
name : string ;
}
function UserList () {
const users : User [] = [{ id: 1 , name: 'John' }];
return (
< List
data = { users }
renderItem = { user => < Text >{user. name } </ Text > }
keyExtractor = { user => user . id . toString ()}
/>
);
}
Utility Types
interface User {
id : number ;
name : string ;
email : string ;
password : string ;
}
// Omit sensitive fields
type PublicUser = Omit < User , 'password' >;
// Pick specific fields
type UserPreview = Pick < User , 'id' | 'name' >;
// Make all fields optional
type PartialUser = Partial < User >;
// Make all fields required
type RequiredUser = Required < Partial < User >>;
// Make all fields readonly
type ImmutableUser = Readonly < User >;
Testing with TypeScript
import { render , fireEvent } from '@testing-library/react-native' ;
import { Button } from './Button' ;
describe ( 'Button' , () => {
it ( 'calls onPress when pressed' , () => {
const onPress = jest . fn ();
const { getByText } = render (
< Button title = "Click" onPress = { onPress } />
);
fireEvent . press ( getByText ( 'Click' ));
expect ( onPress ). toHaveBeenCalledTimes ( 1 );
});
});
Configuration Tips
Strict Mode
Enable strict type checking:
{
"compilerOptions" : {
"strict" : true ,
"noImplicitAny" : true ,
"strictNullChecks" : true ,
"strictFunctionTypes" : true ,
"noUnusedLocals" : true ,
"noUnusedParameters" : true
}
}
Path Mapping
{
"compilerOptions" : {
"baseUrl" : "./src" ,
"paths" : {
"@components/*" : [ "components/*" ],
"@utils/*" : [ "utils/*" ],
"@hooks/*" : [ "hooks/*" ]
}
}
}
Usage:
import { Button } from '@components/Button' ;
import { useAuth } from '@hooks/useAuth' ;
Common Issues
Type errors with third-party libraries
Install type definitions: npm install --save-dev @types/library-name
Or create custom type declarations in types.d.ts: declare module 'library-name' {
export function someFunction () : void ;
}
Cannot find module errors
Ensure moduleResolution is set correctly: {
"compilerOptions" : {
"moduleResolution" : "node"
}
}
Best Practices
Enable strict mode : Catch more errors at compile time
Avoid any type : Use unknown or proper types instead
Use interfaces for props : More readable and maintainable
Type custom hooks : Ensure type safety throughout
Create type utilities : Reuse common type patterns
Document with JSDoc : Add comments for complex types
Use type guards : Safely narrow types at runtime
Next Steps
Testing Test TypeScript React Native apps
Security Security best practices