Home / Articles

Upgrading a React Codebase to TypeScript 6.0: What Breaks First

How TypeScript 6.0's stricter defaults, method inference fix, #/ subpath imports and Temporal types affect a React app, plus a safe upgrade checklist.

Publisher: REACTAPP.TOP4 min read842 words

This article is published in English.

Sources: TypeScript 6.0 for React Developers: What Actually Changed

Most compiler upgrades pass unnoticed. TypeScript 6.0 is different: it is the last release built on the original JavaScript compiler and exists largely to prepare codebases for the Go-based TypeScript 7, so it brings fewer features and more changes that surface existing problems. This guide covers what a React codebase will actually feel and ends with an upgrade checklist. For how 6.0 fits into the native-compiler transition, see TypeScript 6's bridge role in the path to a native TS 7 compiler.

Stricter defaults come first

TypeScript 6.0 turns strict mode on by default, assumes ES modules, and moves the default target to es2025. Projects that set every option explicitly are unaffected; projects that relied on the old permissive defaults will see a wave of new errors. In React code the usual casualties are older components: loosely typed props, handlers with implicit any parameters, refs that may be null, and useState() without a type argument. These are not regressions but long-standing ambiguities that are now reported.

The es3 and es5 targets are also deprecated, so the minimum becomes es2015. For many React apps this changes little, because Babel, SWC or esbuild does the syntax lowering and tsc only type-checks, but confirm that before rethinking your browser support. Run the upgrade on a separate branch with the full build and tests; flipping compiler defaults on a Friday afternoon is a reliable way to lose a weekend.

Consistent inference for method-style functions

A quieter fix concerns when a function counts as "contextually sensitive", meaning its parameter types must come from context before inference can continue. In 6.0, a function expression or method shorthand is only treated this way when it actually uses this. Before, picking method syntax over an arrow function could change generic inference even when this was never touched. The hook below mixes both styles:

// Before: inconsistent inference depending on syntax
const useCounter = (initial) => {
  const [count, setCount] = useState(initial);

  // this pattern used to infer differently than an arrow fn
  const increment = function () {
    setCount((c) => c + 1);
  };

  return { count, increment };
};

Previously, a callback like increment could be inferred differently from an equivalent arrow function, and in larger hooks that difference could leak a weaker type to callers. Now the syntax you choose for callbacks and returned helpers no longer changes the resulting types, which removes a confusing source of "why is this one callback unchecked?" bugs.

Cleaner imports with the #/ prefix

Node's subpath imports define private aliases in the imports field of package.json, and TypeScript 6.0 supports the #/ prefix there, matching the root aliases bundlers commonly use. Declare the mapping once:

{
  "imports": {
    "#/*": "./src/*"
  }
}

Components then import from the project root instead of counting ../ segments:

// No more relative path spaghetti in deeply nested components
import { useAuth } from "#/hooks/useAuth";
import { Button } from "#/components/ui/Button";

Because the alias lives in package.json rather than only in tsconfig.json paths, Node and TypeScript resolve it identically, with no extra plugin. It only applies under resolution modes that read package.json imports, such as nodenext or bundler.

Temporal types ship built in

TypeScript 6.0 includes declarations for the Temporal API, which has reached the final stage of TC39 standardization. Teams building scheduling, booking or dashboard UIs can type Temporal.PlainDate or Temporal.ZonedDateTime without an extra package, instead of fighting Date. The types do not provide the runtime, though: check native support in your target browsers and Node versions and add a polyfill where needed.

Should you upgrade now?

Yes, deliberately. The TypeScript team presents 6.0 as a stable upgrade, but the changes underneath are real:

  • Audit tsconfig.json and pin every option you rely on.
  • Fix new strict-mode errors in older components instead of silencing them with any.
  • Confirm which tool performs downleveling and what target it needs.
  • For codebases with class components or class-driven stores, consider enabling noImplicitOverride so overridden members require override.

Wrapping up

TypeScript 6.0 will not change how you write React, but it exposes the debt that loose defaults were hiding. Paying it down now, on your own schedule, is far cheaper than doing it later in the year while also jumping from 6 to 7, and it turns that later move into mostly a performance story.