1. 09:24 11/10/2026
When you work on both React Web and React Native projects, keeping type definitions in sync can quickly become a maintenance nightmare. Fortunately, TypeScript’s rich collection of utility types lets you write reusable, declarative contracts that work across platforms without copying code. Below is a practical guide to using those utilities effectively.
**1. Centralize Core Types
Start by defining a single source of truth for domain models. Use type aliases to expose shapes that both the web and native layers can import.
```ts
// types/common.ts
export type UserId = string;
export type Email = string;
export interface User {
id: UserId;
email: Email;
name: string;
avatarUrl?: string;
}
```
Because the file is plain TypeScript, it can be referenced from `src/types.ts` in the web project and from `src/types.native.ts` in the React Native app. This eliminates duplicate property lists and guarantees that any update is applied everywhere at once.
**2. Shape API Responses with Generic Utilities
Often the biggest duplication occurs in response shapes. Using `Pick`, `Omit`, and generic parameters you can create flexible response types that adapt to each endpoint.
```ts
export type ApiResponse<T> = {
success: true;
data: T;
} | {
success: false;
error: string;
};
// Example usage
const fetchUser: ApiResponse<User> = await api.get('/user/123');
```
If you later add pagination, you can reuse `ApiResponse` without touching the consuming components.
**3. Build Platform‑Specific Props with Conditional Types
Sometimes a component needs extra props only on native, such as `imageStyle` for an Image component. Conditional types let you express this cleanly.
```ts
type PlatformProps<P> = P extends { platform: 'native' }
? { nativeOnly?: boolean }
: {};
export type CombinedProps<P> = PlatformProps<P> & P;
```
Now you can write a generic component that accepts a `platform` flag and automatically adds the native‑only prop when needed.
**4. Reduce Form Validation Boilerplate
Form libraries like React Hook Form benefit from shared validation schemas. Using `Partial`, `Pick`, and `Required` you can create reusable field types.
```ts
export type BaseField = {
name: string;
label: string;
};
export type TextField = BaseField & {
type: 'text';
validation?: { maxLength?: number };
};
export type Field = TextField | OtherFieldTypes;
```
You can then map `Field` to validation rules once and reuse them in both web and native forms.
**5. Keep Types Lean with Utility Type Combinations
Avoid deep intersection types (`&`) that make debugging harder. Instead, compose smaller utilities:
- `Partial<T>` for optional sections.
- `Pick<T, K>` to expose only needed keys.
- `Omit<T, K>` to hide implementation details.
- `ReturnType<F>` when you need the shape of a function’s result.
By chaining these, you get precise, self‑documenting types without verbose inline definitions.
**Best Practices to Remember**
- Place all shared types in a dedicated folder (e.g., `src/shared/types`) and export them via a barrel file.
- Run `tsc --noEmit` as part of your CI pipeline to catch type mismatches early.
- Use the `exactOptionalPropertyTypes` flag to enforce clearer optional property semantics.
- Refactor when you see duplicate type blocks; the utility types are there to keep your codebase DRY.
By treating types as a first‑class asset and leveraging TypeScript utilities, you gain a single source of truth, smoother refactoring, and safer cross‑platform development.