Working with TypeScript
1. Configuring TypeScript
Example: Initial Setup
npm install -g typescript
npm install --save-dev @types/react @types/react-native
| Configuration File | Location | Description |
|---|---|---|
| tsconfig.json | project-root/tsconfig.json |
Root configuration file for TypeScript compiler options |
Example: Basic tsconfig.json
{
"compilerOptions": {
"target": "es2017",
"lib": [
"es6"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-native"
},
"include": [
"src"
],
"exclude": [
"node_modules"
]
}
Note: Ensure
jsx is set to react-native for React Native projects.
Example: Enabling Strict Mode
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
2. Typing Component Props
| Prop | Type | Description |
|---|---|---|
| children | ReactNode |
Content passed as children to the component |
| className | string |
HTML class attribute for styling |
| style | ViewStyle | TextStyle | ImageStyle |
Inline styles for the component |
Example: Typing a Component with Props
import React from 'react';
import { Text, View } from 'react-native';
interface GreetingProps {
name: string;
age?: number;
}
const Greeting: React.FC<GreetingProps> = ({ name, age }) => (
<View>
<Text>Hello, {name}!</Text>
{age && <Text>Age: {age}</Text>}
</View>
);
export default Greeting;
Example: Using Children with Type
import React from 'react';
import { View } from 'react-native';
interface ContainerProps {
children: React.ReactNode;
}
const Container: React.FC<ContainerProps> = ({ children }) => (
<View style={{ padding: 10 }}>
{children}
</View>
);
export default Container;
3. Typing Navigation Params
When using TypeScript with React Navigation, it's crucial to type your navigation parameters to ensure type safety and better developer experience.
| Parameter | Type | Description |
|---|---|---|
| navigation | NavigationProp<ParamListBase> |
The navigation object provided by React Navigation |
| route | RouteProp<ParamListBase> |
The route object containing the current screen's parameters |
Example: Typing Navigation Parameters
import { NavigationProp, RouteProp } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
type HomeScreenParams = {
userId: string;
userName: string;
};
type HomeScreenNavigationProp = StackNavigationProp<HomeStackParamList, 'Home'>;
type HomeScreenRouteProp = RouteProp<HomeStackParamList, 'Home'>;
const HomeScreen: React.FC<{
navigation: HomeScreenNavigationProp;
route: HomeScreenRouteProp;
}> = ({ navigation, route }) => {
const { userId, userName } = route.params;
return (
<View>
<Text>User ID: {userId}</Text>
<Text>User Name: {userName}</Text>
</View>
);
};
Example: Defining Navigation Parameter Types
import { ParamListBase } from '@react-navigation/native';
export type HomeStackParamList = {
Home: {
userId: string;
userName: string;
};
Profile: {
userId: string;
};
};
export type ProfileStackParamList = {
Profile: {
userId: string;
userName: string;
};
};
4. Typing Hooks
| Hook | Type Signature | Description |
|---|---|---|
| useState | useState<T>(initialValue: T | (() => T)): [T, Dispatch<SetStateAction<T>>] |
State hook with generic type T |
| useEffect | useEffect(effect: () => void | (() => void), deps?: DependencyList): void |
Effect hook with optional dependencies |
| useContext | useContext<T>(context: Context<T>): T |
Context hook with generic type T |
| useReducer | useReducer<S, A>(reducer: Reducer<S, A>, initialState: S): [S, Dispatch<A>] |
Reducer hook with state and action types |
| useCallback | useCallback<T>(callback: T, deps: DependencyList): T |
Callback hook with dependency array |
| useMemo | useMemo<T>(factory: () => T, deps: DependencyList): T |
Memoized value with dependency array |
| useRef | useRef<T>(initialValue: T): MutableRefObject<T> |
Ref hook with generic type T |
Example: Typing useState
interface User {
id: number;
name: string;
}
const [user, setUser] = useState<User | null>(null);
Example: Typing useEffect
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
Example: Typing useContext
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType>({
theme: 'light',
toggleTheme: () => {},
});
const { theme } = useContext(ThemeContext);
Example: Typing useReducer
type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' };
const reducer = (state: number, action: Action): number => {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
};
const [count, dispatch] = useReducer(reducer, 0);
5. Using Generic Components
Generic components in React Native with TypeScript allow you to create reusable components that work with multiple types, enhancing type safety and code reusability.
| Property | Type | Description |
|---|---|---|
| children | React.ReactNode |
Render content inside the component |
| style | StyleProp<ViewStyle> |
Styling for the component |
Example: Generic Component with Props
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
interface GenericComponentProps<T> {
data: T;
renderItem: (item: T) => React.ReactNode;
}
const GenericComponent = <T>(props: GenericComponentProps<T>) => {
return (
<View style={styles.container}>
{props.data.map((item, index) => (
<View key={index}>
{props.renderItem(item)}
</View>
))}
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 10,
},
});
Example: Using Generic Component with Specific Type
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
const UserList = () => {
return (
<GenericComponent<User>
data={users}
renderItem={(user) => (
<Text style={styles.text}>{user.name}</Text>
)}
/>
);
};
const styles = StyleSheet.create({
text: {
fontSize: 16,
},
});
6. Typing Context Values
| Property | Type | Description |
|---|---|---|
| ContextType | React.ContextType<T> |
Type of the context value provided by React.createContext |
| useContext | (context: React.Context<T>) => T |
Hook to consume a context value |
Example: Typing Context Values
import React, { createContext, useContext } from 'react';
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType>({
theme: 'light',
toggleTheme: () => {},
});
const useTheme = () => useContext(ThemeContext);
export default useTheme;
Note: When typing context values, ensure that the default value matches the expected type to avoid runtime errors.
7. Typing Native Modules
When working with native modules in React Native, TypeScript can help ensure type safety and better developer experience. Native modules are typically implemented in Objective-C/Swift (iOS) or Java/Kotlin (Android).
| Module | TypeScript Support | Notes |
|---|---|---|
| Native Module Interface | declare module 'react-native' |
Define native modules in TypeScript using declaration files |
| Method Signatures | method(param: string): Promise<boolean> |
Define return types and parameter types for native methods |
Example: Typing a Native Module Method
declare module 'react-native' {
interface NativeModule {
isDeviceSecure(): Promise<boolean>;
}
}
const NativeModule: NativeModule = require('react-native').NativeModules.NativeModule;
export const isDeviceSecure = (): Promise<boolean> => {
return NativeModule.isDeviceSecure();
};
Note: Ensure that your native module's method signatures match the TypeScript definitions to avoid runtime errors.
Warning: Type definitions for native modules must be updated whenever the native implementation changes to maintain type safety.
8. Using Utility Types
| Utility Type | Description | Example |
|---|---|---|
| Partial<T> | Makes all properties in T optional | interface User { name: string; age: number; }<br>type PartialUser = Partial<User>; // { name?: string; age?: number; } |
| Required<T> | Makes all properties in T required | interface User { name?: string; age?: number; }<br>type RequiredUser = Required<User>; // { name: string; age: number; } |
| Readonly<T> | Makes all properties in T readonly | interface User { name: string; age: number; }<br>type ReadonlyUser = Readonly<User>; // { readonly name: string; readonly age: number; } |
| Pick<T, K> | Constructs a type by picking the set of properties K from T | interface User { name: string; age: number; email: string; }<br>type UserEmail = Pick<User, 'email'>; // { email: string; } |
| Omit<T, K> | Constructs a type by picking all properties from T except K | interface User { name: string; age: number; email: string; }<br>type UserWithoutEmail = Omit<User, 'email'>; // { name: string; age: number; } |
| Record<K, T> | Constructs a type with a set of properties K of type T | type UserMap = Record<'admin' | 'user', string>; // { admin: string; user: string; } |
| Exclude<T, U> | Constructs a type by excluding from T those types that are assignable to U | type Union = 'a' | 'b' | 'c';<br>type Excluded = Exclude<Union, 'a'>; // 'b' | 'c' |
| Extract<T, U> | Constructs a type by extracting from T those types that are assignable to U | type Union = 'a' | 'b' | 'c';<br>type Extracted = Extract<Union, 'a' | 'b'>; // 'a' | 'b' |
| NonNullable<T> | Constructs a type by excluding null and undefined from T | type NullableString = string | null | undefined;<br>type NonNullableString = NonNullable<NullableString>; // string |
| Parameters<T> | Extracts the parameter types from a function type | type Fn = (a: number, b: string) => boolean;<br>type ParamTypes = Parameters<Fn>; // [number, string] |
| ReturnType<T> | Extracts the return type from a function type | type Fn = (a: number, b: string) => boolean;<br>type ReturnTypes = ReturnType<Fn>; // boolean |
Example: Using Pick to create a form type
interface User {<br> id: number;<br> name: string;<br> email: string;<br> password: string;<br>}<br><br>// Create a form type with only name and email<br>type UserForm = Pick<User, 'name' | 'email'>;
Example: Using Partial to make a type optional
interface User {<br> name: string;<br> age: number;<br>}<br><br>// Make all properties optional<br>type PartialUser = Partial<User>;
Note: These utility types are part of the built-in TypeScript library and can be used in any TypeScript project.
9. Implementing Strict Type Checking
Example: Enabling Strict Mode in tsconfig.json
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
| Option | Description | Default |
|---|---|---|
| strict | Enables all strict type checking options | false |
| strictNullChecks | Ensures null and undefined are handled explicitly | false |
| strictFunctionTypes | Checks function parameter types in strict mode | false |
| strictBindCallApply | Checks types for bind, call, and apply methods | false |
| strictPropertyInitialization | Ensures class properties are initialized in constructor | false |
| noImplicitAny | Reports error when inferring any type | false |
| noImplicitReturns | Ensures all code paths return a value | false |
| noImplicitThis | Reports error when this has implicit any type | false |
| noUnusedLocals | Reports error for unused local variables | false |
| noUnusedParameters | Reports error for unused function parameters | false |
Note: Enabling strict mode helps catch type-related bugs early in development.
Warning: Enabling strict mode might require updating existing code to handle type errors.
10. Handling Third-Party Types
| Package | Type Definition | Notes |
|---|---|---|
| @types/react-native | react-native |
Official type definitions for React Native |
| @types/react | react |
Type definitions for React core |
Note: Ensure that type definitions match the version of React Native you are using.