react: Bug: React 18 types broken since the type release a view hours ago

Edit: If you have issues but you did NOT upgrade to 18, or if you upgraded but get confusing errors from dependencies, the problem is likely that some library (incorrectly) specifies @types/react as a dependency with version * rather than an optional peer dependency. Find which library it is and file an issue for it. See https://github.com/facebook/react/issues/24304#issuecomment-1094565891 for diagnostics and common workarounds.

A few hours ago, a major version of React types for Typescript was released.

I have tried to test this right away to see if there are any changes that require adaptation in my own projects.

Due to a very large number of users using React’s type library (we use fundamental types like React.FC by the hundreds in our own projects), it’s reasonable to question whether there’s been a small mistake here.

Specifically, the following type declaration still exists in the 18 release.

type PropsWithChildren<P> = P & { children?: ReactNode | undefined };

However, this is no longer used in the library, at least not when looking at the diff https://github.com/DefinitelyTyped/DefinitelyTyped/commit/55dc209ceb6dbcd59c4c68cc8dfb77faadd9de12

A quick search on Github yields millions of hits that use the type alias React.FC to the actual type React.FunctionalComponent.

Before

interface FunctionComponent<P = {}> {
      (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
      ...
}

Now

interface FunctionComponent<P = {}> {
      (props: P, context?: any): ReactElement<any, any> | null;
}

I think matching very, very many places in a single project doesn’t seem to do justice to the change made; always the property children?: ReactNode to be redefined is definitely too much effort.

Positions at which PropsWithChildren were used before

  • FunctionComponent, as shown in the example
  • ForwardRefRenderFunction,
  • und das Funktions-Interface propsAreEqual von memo

Thx!

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 46
  • Comments: 47 (3 by maintainers)

Commits related to this issue

Most upvoted comments

quick and dirty, install npm-force-resolutions and add resolution for all of @types/react

on scripts,

"preinstall": "npx npm-force-resolutions"

on resolutions

"@types/react": "17.0.30"

Incompatible versions of @types/react being installed is a recurring problem. We didn’t release breaking changes in the types for quite some time now so I forgot this issue existed. This is an oversight on my part that we should’ve prepared for before releasing.

Declaring @types/react as an (optional) peer dependency is the correct solution. We’ve been doing this in MUI for a while now.

The reason you’re seeing "@types/react": "*" in regular dependencies is most likely because it was copied over from packages published from the DefinitelyTyped repo.

We’ve had this discussion before (https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33015 and others I believe) and are currently waiting for https://github.com/microsoft/DefinitelyTyped-tools/issues/433 (continued from https://github.com/microsoft/types-publisher/issues/431).

I’ll write up an extensive use case for why we want optional peer dependencies to restart the discussion around dependencies vs peerDependencies and if we can avoid "*" ranges in the future.

workarounds

Yarn v1

npx yarn-deduplicate --packages @types/react for now. Any package requiring v17 explicitly should be asked to either bump it (if they support React 18) or you should find an alternative library anyway if you want to use React 18.

npm v8

You can use overrides like so:

{
  "overrides": {
    "@types/react": "^18.0.0"
  }
}

Note that you might still have to do some manual package-lock.json fixing with npm@8.5.0 (and below) until https://github.com/npm/cli/pull/4599 is released.

npm before v8

For npm before v8 I don’t have a good solution. The only possible workaround is going nuclear and deleting package-lock.json and running npm install again

yarn v2 and above

Similar to Yarn v1: yarn dedupe '@types/react'. Any package requiring v17 explicitly should be asked to either bump it (if they support React 18) or you should find an alternative library anyway if you want to use React 18.

I totally understand that breaking changes are needed sometimes.

I only want to suggest that the next time you add a deprecation warning with a /**@deprecated*/ jsdoc tag in an intermediate release before releasing the breaking changes.

I would have stopped using this syntax a long time ago if my editor started warning me this was deprecated syntax.

Also if this bothers anyone then you really don’t have to upgrade right away - let the ecosystem figure the final way forward first and only attempt to upgrade later on. React 18 has been released just two weeks ago.

quick and dirty, install npm-force-resolutions and add resolution for all of @types/react

on scripts,

"preinstall": "npx npm-force-resolutions"

on resolutions

"@types/react": "17.0.30"

if you use yarn, just use the resolutions, no need for npm-force-resolutions

resolutions is a temporary solution

"resolutions": {
    "@types/react": "17.0.2", // your version
	}

Still not fixed.

There’s nothing to be fixed here. Please read the discussion.

I’ll close this issue to make it clearer.

Here you go, this worked in my project.

// react.d.ts
import { FunctionComponent, ReactNode } from 'react';

declare module 'react' {
  declare namespace React {
    type FC<P = {}> = FunctionComponent<PropsWithChildren<P>>;
  }

  export = React;
  export as namespace React;
}

@KutnerUri

I understand the frustration, but this is the tradeoff you’re opting into by using TypeScript. We need to be able to fix bugs in definitions. Ultimately, if upgrading types is too much of a burden, it might be worth questioning whether TypeScript is really beneficial for your project.

Regarding gradual upgrades, 17 makes it possible by fixing the event system quirks. This doesn’t mean that we provide anything special for TypeScript definitions. You can file an issue in the TypeScript repo about this if you’d like to ask if there are good solutions for mixing versions. If you’re blocked by this, you can also upgrade React but keep all your types as 17, and then suppress typing errors everywhere that new methods are used.

In general though, we don’t think of the “mixed versions” upgrade strategy as very important for 18. It’s nice that it works, but in practice we’ve reduced the actual runtime breaking changes enough that in terms of effort, upgrading to 18 is comparable to our other major releases. So we’d suggest just upgrading the app at once.

Candidate for worst design choice of the year. Nothing better than being locked into this ecosystem.

Now every FC definition is broken, and you’ll have to manually specify props already inherent to a component like children. It’s actually such a mess that even trying to freeze your versions to v17 will result in a type error over this because @types/react-dom@17.0.2 depends on @types/react@* which pulls v18 types (see multiple SO issues).

What possible benefit is received in exchange for all of this?

For pnpm users:

"pnpm": {
  "overrides": {
    "@types/react": "17.0.37",
    "@types/react-dom": "17.0.11"
  }
}

I agree all of this sounds sad. However, in principle we need to be able to make breaking changes in major versions of types. I wonder what are the best practices around this in the TypeScript ecosystem, given that we are unlikely to be the first library that’s a common peer and that made a breaking change in the type definition to fix an earlier mistake? One thing we could’ve done better on our side is to surface the list of TS breaking changes earlier. They were public (for over a year), but they were in a different repository, so I understand if people haven’t seen them. (I saw… a hundred?.. typing PRs linked in https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56210 by @eps1lon, and thank him for that heroic effort). We could have surfaced this work more (e.g. on our blog). Are there other things that we can learn from this? I appreciate your patience.

@gaearon This is sorta necessary for libraries that ship TS types. TS compiles app source, which refers to LibA types, which point to React types. So, either you explicitly say “LibA depends on @types/react” to ensure that they get installed, or you peerDep them.

I was actually just debating this with @Andarist for React-Redux v8, and what we settled on was an optional peer dep:

  "peerDependencies": {
    "react": "^18.0.0"
  },
  "peerDependenciesMeta": {
    "@types/react": {
      "optional": true
    },

so we’re assuming that the user will already have those types installed if they plan on using React-Redux with TS. But, given that this is a relatively newer thing for package.json (?), it’s very understandable that a bunch of libs would have explicitly listed that as a standard dep in order to make sure it gets installed into the end user’s project.

this is not such a good solution, and it mostly works in monolith projects. wasn’t React 18 supposed to work side by side with previous versions of React, allowing smoother gradual upgrades?

because right now it seems like we are forced to use either v18 or v17

Hey all, I want to provide some context from the TypeScript/DT side.

First, the current state of the world doesn’t have an immediate fix that makes everything good and easy again. I don’t think we can feasibly do anything without breaking other existing users. I’m sorry that things have ended up rocky here.

If you’re dealing with duplicate installations of @types/react, the best guidance I can provide now is to either create a new lockfile, or use the overriding or deduplicating logic of your package manager, as described here here.

Switching existing packages to move @types/react from dependencies to peerDependencies would break a different group of users in a different way (though admittedly, those who would be broken have arguably questionable package.jsons). What’s more, the behavior of package managers and their versions varies a decent amount, regardless of newer fields like peerDependenciesMeta. So changing an existing package over to peerDependencies now would be an arguably another break. So what can we do?

It may be worth revisiting peerDependencies long-term and for newer packages. That discussion is taking place over at https://github.com/microsoft/DefinitelyTyped-tools/issues/433. That will take some figuring out, and it’ll take time for the team to (re)build context on each of the problems that that would introduce (including UX issues, automatic type acquisition issues, etc.). I can’t give a timeline on that investigation right now, but it seems like a reasonable long-term investment to unlock libraries from using it. But it’s not clear at this point in time whether it’s something we’d want every React consumer to use in place of regular dependencies.

Otherwise this thread is fairly long, and it doesn’t seem to be getting any more productive. It may be worth locking the thread so users can easily see the workarounds I’ve mentioned above.

I want to reiterate that using TypeScript is a choice. You don’t have to use React with TypeScript. If you choose to use React with TypeScript, the problems with the TypeScript ecosystem will affect you. The pervasiveness of the * dependency range is a problem with the TypeScript ecosystem, and you should bring it up with the TypeScript project. We don’t have the leverage to fix this.

We had the same problem today. In our stack we are using NextJS with React 17. One dependency in the project is using "@types/react": "*" so it was resolving to v18.

I have forced to use version 17 of the package with:

npm i @types/react@17.0.39 --D --force

and it stopped resolving to version 18, so it is now working again.

Maybe it will help you

I was able to resolve this issue with a package.json change:

“optionalDependencies”: { “@types/react”: “^17.0.44” },

Still not fixed.

I’m not sure I understand why a library would need to include React types in its dependencies at all.

@testing-library/react does this. Here’s the Issue (links to PR) describing why they had to do that https://github.com/testing-library/react-testing-library/issues/1000

because @types/react-dom@17.0.2 depends on @types/react@* which pulls v18 types

The workarounds in https://github.com/facebook/react/issues/24304#issuecomment-1094565891 address this. It sucks that there need to be workarounds, but ultimately we need to be able to make breaking changes to types in major versions. If you dislike the experience, the TypeScript and/or DefinitelyTyped tooling repositories are the right place to complain, since the practice of using * for versions is not encouraged by npm and comes directly from DefinitelyTyped. This practice is wrong and leads to consequences like this.

What possible benefit is received in exchange for all of this?

The major benefit is that the compiler no longer lets objects through (which crash at runtime). The issue is described in https://fettblog.eu/react-types-for-children-are-broken/.

ok I think I found the breaking change:

// in @types/react v17:
type ReactFragment = {} | ReactNodeArray;

// in @types/react v18:
type ReactFragment = Iterable<ReactNode>;

which is used in

DOMAttributes {
  children?: ReactNode;
}

declare global {
  namespace JSX {
    interface IntrinsicElements {
      div: DetailedHTMLFactory<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
      ...
    }
  } 
}

I’m trying to see if I create a compatibility layer, like react.compat.d.ts, or force typescript to use @types/react 18 even thought it’s spanning two projects.

I wish JSX was not global and so would not effect the project still using @types/react v17 😭

quick and dirty, install npm-force-resolutions and add resolution for all of @types/react

on scripts,

"preinstall": "npx npm-force-resolutions"

on resolutions

"@types/react": "17.0.30"

I got more errors after applying this method

If I understand correctly, the issue you’re referring to is https://solverfox.dev/writing/no-implicit-children/. It’s not strictly related to React 18 per se, but it’s a breaking change the maintainers of React typings have meant to do for a few years. I don’t want to speak for them — but there’ll probably never be a particularly “good time” to do it, and as it stands the types were wrong. So at some point there is a need to bite the bullet. It looks like there is an automated conversion available so maybe this is something you can try: https://github.com/eps1lon/types-react-codemod#implicit-children.