Web Development

    TypeScript Best Practices for React Development

    TypeScript catches bugs before they reach users. These best practices show how to get maximum type safety with minimum boilerplate in React projects.

    VT
    11 min read
    Share

    Key Takeaways

    • Avoid 'any' at all costs; use 'unknown', union types, or generics for truly unknown values
    • Discriminated unions model component variants more safely than optional props with invalid combinations
    • Zod provides both runtime validation and TypeScript type generation, ensuring API responses match types
    • Enable strict mode and noUncheckedIndexedAccess for maximum type safety in tsconfig
    • Frequent use of type assertions (as) is a code smell indicating incorrect type definitions

    Typing React Components Effectively

    Properly typed components serve as self-documenting contracts. Clear prop interfaces tell other developers exactly what a component expects, preventing bugs and reducing the need for documentation.

    PatternUse CaseExample
    Interface for propsAll componentsinterface ButtonProps { variant: 'primary' | 'secondary' }
    React.FC (avoid)Legacy codebasesImplicitly adds children, less explicit
    Discriminated unionsVariant componentstype Props = { type: 'link'; href: string } | { type: 'button'; onClick: () => void }
    Generic componentsReusable data componentsfunction List<T>({ items }: { items: T[] })
    PropsWithChildrenWrapper componentstype Props = PropsWithChildren<{ title: string }>
    Skip React.FC

    The TypeScript community and React team recommend plain function declarations over React.FC. FC implicitly includes children (even when not wanted) and makes generics awkward. Use `function Button(props: ButtonProps)` instead.

    • Name prop interfaces descriptively: UserCardProps, not Props or IProps
    • Export prop interfaces so parent components can reference them
    • Use discriminated unions when component behavior changes based on a prop variant
    • Prefer interfaces over types for props (better error messages, extendable)

    Advanced TypeScript Patterns for React

    Advanced TypeScript patterns reduce boilerplate while maintaining strong type safety. These patterns handle complex scenarios like polymorphic components, generic hooks, and type-safe event systems.

    Utility TypePurposeExample Usage
    Partial<T>Make all properties optionalPartial<UserProfile> for update forms
    Pick<T, K>Select specific propertiesPick<User, 'name' | 'email'>
    Omit<T, K>Exclude specific propertiesOmit<User, 'password'>
    Record<K, V>Dictionary typeRecord<string, ComponentType>
    Extract<T, U>Extract matching union membersExtract<Status, 'active' | 'pending'>
    ReturnType<T>Infer function return typeReturnType<typeof useAuth>
    Type Assertion Smell

    If you use `as` more than once per file, your types likely do not match reality. Instead of asserting, fix the type definitions to accurately represent your data. Type assertions bypass the compiler and hide potential bugs.

    Runtime Validation with Zod

    TypeScript types are erased at runtime. API responses, form inputs, and external data can violate your type definitions without any runtime error. Zod bridges this gap with runtime validation that generates TypeScript types.

    1. Define Zod schemas for all API response shapes
    2. Use z.infer<typeof schema> to derive TypeScript types from Zod schemas
    3. Validate API responses at the boundary (fetch wrapper, API client)
    4. Use Zod schemas with react-hook-form for type-safe form validation
    5. Create shared schemas between frontend and backend for end-to-end type safety
    Single Source of Truth

    With Zod, the schema is the single source of truth for both runtime validation and TypeScript types. This eliminates the common problem of TypeScript interfaces drifting from actual API response shapes.

    TypeScript Configuration for Maximum Safety

    TypeScript's default configuration is permissive. Enabling strict mode and additional checks catches significantly more bugs at compile time, reducing runtime errors in production.

    SettingWhat It DoesRecommendation
    strict: trueEnables all strict checksAlways enable
    noUncheckedIndexedAccessArray/object access returns T | undefinedEnable (catches null errors)
    exactOptionalPropertyTypesDistinguishes undefined from missingEnable for new projects
    noImplicitReturnsAll code paths must returnEnable
    forceConsistentCasingInFileNamesPrevents case-sensitivity bugsAlways enable
    • Start new projects with strict: true from day one
    • For existing projects, enable strict checks incrementally to avoid overwhelming changes
    • noUncheckedIndexedAccess is the single most impactful non-default setting
    • Use // @ts-expect-error (not @ts-ignore) for intentional suppressions with error messages

    Frequently Asked Questions

    Related services from Verbsz Technologies

    If this article is relevant to a project on your roadmap, these are the services our team typically delivers.

    Need Help Implementing This?

    Our team can help you put these insights into practice.

    Schedule a Consultation