Skip to content

Conversation

@eps1lon
Copy link
Collaborator

@eps1lon eps1lon commented Mar 17, 2024

19.0.0-rc.1 has been released and if nothing urgent comes up, will be the final React 19 release soon.
Please do not merge this just yet.

Changelog

Table of Contents

In recent years, the API surface of @types/react has gotten larger than we'd like.
This makes working with React and TypeScript unnecessarily tricky.
So, we're taking this opportunity to make some small breaking changes to our types, deprecating some and removing others.

We've wanted to make the migration easier - most can be automated with codemods.
Check out the migration guide below for more information.

The runtime changes are far more important since they directly affect the end user of your application.
They'll be detailed in React's release notes.

Migrating

While we have included some breaking changes, most of them can be resolved with codemods to keep manual work to a minimum.

Quick Explanation

# Run the codemod
npx types-react-codemod@latest preset-19 ./path-to-your-react-ts-files

# If you have a lot of unsound access to element props,
# you can run this additional codemod:
npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files

Full Explanation

Almost all of the @types/* packages are already compatible with React 19 types unless they specifically rely on React 18. Therefore it is advised to upgrade all React-related @types/* packages first.

Apply the preset-19 codemod from types-react-codemod in its default configuration via npx types-react-codemod@latest preset-19 ./path-to-your-react-ts-files.
In our experience, this covers most breaking changes.

The largest block of remaining type issues relate to props of React elements now defaulting to unknown instead of any.
If you're focus is on migration instead of soundness, you can use the react-element-default-any-props to resolve a large portion of the breaking changes related to ReactElement.

However, the codemod can't cover every pattern.
You probably have to manually adjust the lines relying on any in element.props either by additional runtime checking or manual casts to any.
You can check out the example migrations done on libraries e.g. MUI or apps e.g. Bluesky to get an idea of the possible patterns.

Breaking changes

This section focuses on breaking changes for the React types.
Some types have been removed, some type parameters have been changed, and useRef has been simplified.

Removed Types

We've removed these types from @types/react.
Some of them have been moved to more relevant packages, like Validator moving to PropTypes.
Others are no longer needed to describe React's behavior.
Removing them means one less thing to learn.

Codemoddable

Type Codemod Replacement
ReactChild deprecated-react-child React.ReactElement | number | string
ReactFragment deprecated-react-fragment Iterable<React.ReactNode>
ReactNodeArray deprecated-react-node-array ReadonlyArray<React.ReactNode>
ReactText deprecated-react-text number | string
Requireable deprecated-prop-types-types Requireable from prop-types
ValidationMap deprecated-prop-types-types ValidationMap from prop-types
Validator deprecated-prop-types-types Validator from prop-types
VoidFunctionComponent deprecated-void-function-component FunctionComponent
VFC deprecated-void-function-component FC
WeakValidationMap deprecated-prop-types-types WeakValidationMap from prop-types

Not Codemoddable

During our example migrations, these types were not used at all.
So, we felt comfortable leaving them off our codemod list.
If you feel a codemod is missing, it can be tracked in the list of missing React 19 codemods.

Type Replacement
ClassicComponentClass ClassicComponentClass from create-react-class
ClassicComponent ClassicComponent from create-react-class
ClassicElement<Props> ClassicElement<Props, InstanceType<T>> from create-react-class
ComponentSpec ComponentSpec from the create-react-class package
Mixin Mixin from the create-react-class package
ReactChildren typeof React.Children
ReactHTML Either ReactHTML from react-dom-factories or, if you used keyof ReactHTML, use HTMLElementType instead
ReactSVG Either ReactSVG from react-dom-factories or, if you used keyof ReactSVG, use SVGElementType instead
SFCFactory No replacement

JSX Namespace

A long-time request is to remove the global JSX namespace from our types in favor of React.JSX.
This helps prevent pollution of global types which prevents conflicts between different UI libraries that leverage JSX.
This change is codemoddable with scoped-jsx.

This means that if you're doing any module augmentation of JSX:

// global.d.ts

// This adds a new global JSX element
namespace JSX {
  interface IntrinsicElements {
    "my-element": {
      myElementProps: string;
    };
  }
}

You'll now need to wrap it in declare module "....":

// global.d.ts

+ declare module "react" {
    namespace JSX {
      interface IntrinsicElements {
        "my-element": {
          myElementProps: string;
        };
      }
    }
+ }

The exact module specifier depends on the JSX runtime you specified in the compilerOptions of your tsconfig.json.
For "jsx": "react-jsx" it would be react/jsx-runtime.
For "jsx": "react-jsxdev" it would be react/jsx-dev-runtime.
For "jsx": "react" and "jsx": "preserve" it would be react.

Changes to Type Parameters

useReducer

useReducer now has improved type inference thanks to @mfp22.

However, this required a breaking change where we don't accept the full reducer type as a type parameter but instead either want none (and rely on contextual typing) or want both the state and action type.

The new best practice is not to pass type arguments to useReducer.

-useReducer<React.Reducer<State, Action>>(reducer)
+useReducer(reducer)

However, this may not work in edge cases where you can explicitly type the state and action, by passing in the Action in a tuple:

-useReducer<React.Reducer<State, Action>>(reducer)
+useReducer<State, [Action]>(reducer)

If you define the reducer inline, we encourage to annotate the function parameters instead:

-useReducer<React.Reducer<State, Action>>((state, action) => state)
+useReducer((state: State, action: Action) => state)

This, of course, is also what you'd also have to do if you move the reducer outside of the useReducer call:

const reducer = (state: State, action: Action) => state;

ReactElement

The props of React elements now default to unknown instead of any if the element is typed as ReactElement. This does not affect you if you pass a type argument to ReactElement:

type Example2 = ReactElement<{ id: string }>["props"];
//   ^? { id: string }

But if you relied on the default, you now have to handle unknown:

type Example = ReactElement["props"];
//   ^? Before, was 'any', now 'unknown'

If you rely on this behavior, use the react-element-default-any-props codemod.
You should only need it if you have a lot of legacy code relying on unsound access of element props.
Element introspection only exists as an escape hatch and you should make it explicit that your props access is unsound via an explicit any.

Component types

Due to the removal of legacy context, forward ref render functions (e.g. (props: P, ref: Ref<T>) => ReactNode) will now be rejected by TypeScript if used as a component type.

This was almost always a bug that needed fixing by wrapping the render function in forwardRef or removing the second ref parameter.

Ref cleanup

React 19 allows returning a "cleanup function" from callback refs.

<div
  ref={(_ref) => {
    // Use `_ref`

    return () => {
      // Clean up _ref
    };
  }}
/>;

This means returning anything else will now be rejected by TypeScript.

The fix is usually to stop using implicit returns e.g.

-<div ref={current => (instance = current)} />
+<div ref={current => {instance = current}} />

The original code returned the instance of the HTMLDivElement and TypeScript wouldn't know if this was supposed to be a cleanup function or if you didn't want to return a cleanup function.

You can codemod this pattern with no-implicit-ref-callback-return

propTypes and defaultProps statics

propTypes are now ignored by React.
However, to ease migration, we just type propTypes as any to ease migration in case these components are a bridge between typed and untyped components.
If we'd remove propTypes entirely, a lot of assignments would cause TypeScript issues.

The same does not apply to defaultProps on function components since not rejecting them during type-checking would cause actual issues at runtime.
Please check out the changelog entry for the removal of defaultProps to learn how to migrate off of defaultProps.

Ref changes

A long-time complaint of how TypeScript and React work has been useRef.
We've changed the types so that useRef now requires an argument.
This significantly simplifies its type signature. It'll now behave more like createContext.

// @ts-expect-error: Expected 1 argument but saw none
useRef();
// Passes
useRef(undefined);
// @ts-expect-error: Expected 1 argument but saw none
createContext();
// Passes
createContext(undefined);

This now also means that all refs are mutable.
You'll no longer hit the issue where you can't mutate a ref because you initialised it with null:

const ref = useRef<number>(null);

// Cannot assign to 'current' because it is a read-only property
ref.current = 1;

MutableRef is now deprecated in favor of a single RefObject type which useRef will always return:

interface RefObject<T> {
  current: T
}

declare function useRef<T>: RefObject<T>

We still have a convenience overload for useRef<T>(null) that automatically returns RefObject<T | null>.
To ease migration due to the required argument for useRef, we also added a convenience overload for useRef(undefined) that automatically returns RefObject<T | undefined>.

Check out [RFC] Make all refs mutable for prior discussions about this change.

Codemod

When you apply the useRef-required-initial codemod (part of preset-19), all useRef() calls will be converted to useRef(undefined).

Repo stuff, not the changelog

Thanks to @mattpocock for writing the changelog!

Closed issues

Closes #64451
Closes #64896
Closes #64772
Closes #64412
Closes #64920

Reviewer notes

Packages that pin @types/react@^18 either need more work to get compatible or depend on a library that's not hosted in this repo. The other libraries are other compatible or have been made compatible in this PR as a best effort.

For changes to React runtime APIs, please file them as issues to facebook/react instead. Keep the discussion focused on types.

For changelog review, please use eps1lon#35

CI failure related to "React 19 not found on NPM" is expected. All the other failures need to be gone. I'll extracted as many required changes to other packages as possible to earlier PRs.

Maintainer notes

Before merge:

  • "freeze" React changes on master (Basically just don't merge during subsequent tasks. These tasks are small so this shouldn't be an issue).
  • create fork
  • diff ts5.0 fork

Create React 18 fork with https://github.com/eps1lon/react-types-tools/blob/main/createReact18TypesFork.sh

Backport changes to ts5.0 fork with

git diff master HEAD -- types/react | sed "s|a/types/react|a/types/react/ts5.0|g"  | sed "s|b/types/react|b/types/react/ts5.0|g" > backport.diff; git apply --reject backport.diff

@eps1lon eps1lon added pkg: [email protected] Discussions related to the release of React 19.0 and removed pkg: [email protected] labels Mar 17, 2024
@eps1lon eps1lon self-assigned this Mar 17, 2024
@eps1lon eps1lon force-pushed the react/19 branch 15 times, most recently from 6f5e24f to bd58fbf Compare March 23, 2024 20:10
@eps1lon eps1lon force-pushed the react/19 branch 4 times, most recently from 47920ab to 51e726f Compare March 26, 2024 20:20
@eps1lon eps1lon changed the title [draft] Opening to test compat [react] Types for React 19 Mar 26, 2024
@typescript-bot
Copy link
Contributor

@DiegoAndai, @Eyas, @zeorin, @remcohaszing, @karlhorky, @ChristianMurphy Thank you for reviewing this PR! The author has pushed new commits since your last review. Could you take another look and submit a fresh review?

@eps1lon eps1lon dismissed stale reviews from ChristianMurphy and remcohaszing December 5, 2024 19:03

stale

@eps1lon eps1lon merged commit 1e0850c into DefinitelyTyped:master Dec 5, 2024
11 checks passed
@karlhorky
Copy link
Contributor

Amazing, thanks @eps1lon and everyone else who contributed here on the long journey to React 19 stable! 🎉 🚀

Philipp91 added a commit to Philipp91/recharts that referenced this pull request Dec 7, 2024
@eps1lon eps1lon removed their assignment Dec 9, 2024
Robot-Inventor added a commit to Robot-Inventor/mdui that referenced this pull request Feb 26, 2025
The `JSX` namespace in React was deprecated in [DefinitelyTyped PR #64464](DefinitelyTyped/DefinitelyTyped#64464) and removed in [DefinitelyTyped PR #69022](DefinitelyTyped/DefinitelyTyped#69022). Instead, `React.JSX` should be used.
zdhxiong pushed a commit to zdhxiong/mdui that referenced this pull request Feb 27, 2025
The `JSX` namespace in React was deprecated in [DefinitelyTyped PR #64464](DefinitelyTyped/DefinitelyTyped#64464) and removed in [DefinitelyTyped PR #69022](DefinitelyTyped/DefinitelyTyped#69022). Instead, `React.JSX` should be used.
@eps1lon eps1lon deleted the react/19 branch May 20, 2025 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Check Config Changes a module config files Critical package Edits Infrastructure Edits multiple packages Huge Change pkg: [email protected] Discussions related to the release of React 19.0 Too Many Owners Unreviewed No one showed up to review this PR, so it'll be reviewed by a DT maintainer.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants