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.