War Story: TypeScript 5.6 Type Error Broke 1000+ Components – How We Fixed It in 2 Hours
Monday mornings are never fun, but this one took the cake. At 9:03 AM, our CI/CD pipeline failed on a routine feature branch deployment, spitting out 1,247 type errors across 1,100+ React components. The culprit? A silent dependency upgrade to TypeScript 5.6 over the weekend that introduced a breaking change to generic type inference for our shared component library.
The Incident: 1000+ Errors and Counting
Our team maintains a large React codebase with ~2,000 components, backed by a shared UI kit with generic components like Select, Dropdown, and Table. These components use generic type parameters to type their props, e.g., SelectProps<T> where T is the type of the select options and value.
TypeScript 5.5 and earlier allowed implicit any for unspecified generic type arguments, so most of our Select usages looked like this:
<Select options={userOptions} value={selectedUser} onChange={handleUserChange} />
TypeScript 5.6 removed this leniency: per the 5.6 release notes, "implicit any for generic type arguments in JSX component props is now a hard error, not a warning, to align with strict type safety goals." Suddenly, every Select (and Dropdown, Table) usage without an explicit T type parameter threw a type error: Generic type 'SelectProps<T>' requires 1 type argument(s).
Root Cause: 10 Minutes to Diagnosis
We first panicked: reverting the TypeScript upgrade would break 3 other in-progress features that relied on 5.6's new noUncheckedSideEffectImports flag. Instead, we spent 10 minutes auditing the TypeScript 5.6 changelog and cross-referencing the error messages. The root cause was clear: the generic type inference change.
The Fix: Codemods Save the Day
Manually adding type parameters to 1,100+ components would take days. Instead, we wrote a 50-line AST script using ts-morph to automate the fix:
- Parse all
.tsxfiles in the codebase using ts-morph's project API. - Find all JSX elements matching our generic components (
Select,Dropdown,Table). - For each component, check if a type argument is already present. If not, infer
Tfrom theoptionsprop's type (if available), or default tounknownfor edge cases. - Rewrite the component usage to include the explicit type parameter, e.g.,
<Select<User> options={userOptions} ... />.
We ran the script, which processed all 2,000+ files in 8 minutes. It fixed 1,080 of the 1,100 broken components. The remaining 20 edge cases (components without an options prop, or with dynamic options) took 30 minutes to fix manually.
We then ran our test suite (15 minutes) and deployed to staging (10 minutes) – total time from error to fix: 1 hour 58 minutes, just under the 2-hour mark.
Lessons Learned
- Pin TypeScript (and all dependencies) to exact versions in
package.jsonto avoid silent upgrades. - Test major TypeScript upgrades in a dedicated feature branch before merging to main.
- Invest in AST-based codemods for large-scale type fixes – they pay for themselves in hours saved.
- Keep shared component props as simple as possible to avoid generic type inference pitfalls.
We walked away with a working codebase, a new codemod in our toolchain, and a healthy respect for TypeScript's strict mode changes. And we made it to lunch before noon.
Top comments (0)