TypeScript Patterns That Actually Make React Code Better

After years of TypeScript in production React codebases, the patterns worth keeping are the ones that reduce decisions, not the ones that demonstrate type-level cleverness.
TypeScript has won. That's not really a debate anymore. The question most teams are navigating now is not whether to use it, but how to use it in a way that adds more value than friction. Because TypeScript done poorly — with overly clever type gymnastics, redundant annotations, and types that exist to satisfy the compiler rather than to communicate intent — is genuinely worse than well-written JavaScript. The types add noise without adding clarity.
What follows are the patterns I reach for repeatedly in production React codebases. None of them are particularly advanced. That's the point. The goal is types that serve the code, not types that perform sophistication.
The best TypeScript is the kind that makes a wrong state unrepresentable. Not the kind that makes your colleagues Google what a conditional type is.
Component props: precise over permissive
The most common TypeScript mistake in React codebases is props that are too permissive. string when it should be a union of specific strings. any when the shape is actually known. object when you mean a specific interface. Permissive types let bad data through, and they deprive editors of the ability to autocomplete valid values.
Discriminated unions for component state
This is the pattern I wish I had used from day one. When a component has multiple states — loading, error, success, empty — the naive approach is a bunch of booleans: isLoading, isError, hasData. The problem is that booleans can represent impossible states: isLoading: true and isError: true simultaneously. The type doesn't prevent it, so the component has to defensively handle cases that should never occur.
Typing custom hooks properly
Custom hooks are one of the best things React added, and TypeScript makes them significantly better when you type them well. The most useful pattern is explicit return type annotations for hooks that return tuples — without them, TypeScript infers an array type, and you lose the positional typing that makes destructuring safe.
- Discriminated unions for any component with multiple states
- Explicit return type annotations on custom hooks returning tuples
satisfiesoperator to validate object shapes without widening the typeComponentPropsWithoutRef<'button'>to extend native element props safely- Const assertions for config objects that shouldn't be mutated
Extending native element props
A pattern that comes up constantly in component libraries is building a typed wrapper around a native HTML element. You want your Button to accept all the props a native <button> accepts, plus a handful of custom ones. The right approach uses ComponentPropsWithoutRef rather than manually re-declaring every HTML attribute:
Conclusion
The TypeScript patterns worth investing in are the ones that prevent real bugs — impossible states, wrong prop types, missing cases in switch statements. Not the ones that demonstrate how deep the type system goes.
The measure of good TypeScript in a React codebase is whether new team members can look at a component's props and understand its behavior without reading the implementation. If the types serve as accurate, up-to-date documentation, you're doing it right. If they're just annotations that repeat what the variable names already say, you're adding maintenance overhead without the benefit.