preact: [typescript] React.ReactElement type is not compatible with Preact and prevents interoperability with React

There is a type mismatch between React.ReactElement and preact.VNode that prevents usage of typescript/react code with typescript/preact.

While the code below compiles and works without an issue, Typescript typechecking fails.

Reproduction

import type React from 'react'
import { FunctionalComponent, h } from 'preact'

const ReactFCInterlop: React.FC = () => {
    return <span>hello</span>
}

const Home: FunctionalComponent = () => {
    return (
        <div>
            <ReactFCInterlop /> // this fails to typecheck
        </div>
    )
}

export default Home

This results in the following typescript error:

$ tsc
src/routes/home/index.tsx:11:14 - error TS2786: 'ReactFCInterlop' cannot be used as a JSX component.
  Its return type 'ReactElement<any, any> | null' is not a valid JSX element.
    Type 'ReactElement<any, any>' is missing the following properties from type 'Element': nodeName, attributes, children

11             <ReactFCInterlop />
                ~~~~~~~~~~~~~~~

Typescript config:

$ tsc --showConfig
{
    "compilerOptions": {
        "skipLibCheck": true,
        "target": "es5",
        "module": "esnext",
        "allowJs": true,
        "jsx": "preserve",
        "jsxFactory": "h",
        "noEmit": true,
        "strict": true,
        "moduleResolution": "node",
        "esModuleInterop": true
    },
    "files": [
        "./src/components/app.tsx",
        "./src/components/header/index.tsx",
        "./src/routes/home/index.tsx",
        "./src/routes/notfound/index.tsx",
        "./src/routes/profile/index.tsx",
        "./src/tests/header.test.tsx",
        "./src/types.d.ts",
        "./src/components/header/style.css.d.ts",
        "./src/routes/home/style.css.d.ts",
        "./src/routes/notfound/style.css.d.ts",
        "./src/routes/profile/style.css.d.ts",
        "./src/tests/declarations.d.ts"
    ],
    "include": [
        "src/**/*.tsx",
        "src/**/*.ts"
    ]
}

Deps:

$ npm ls --depth 0 | grep preact
├── enzyme-adapter-preact-pure@2.2.3
├── jest-preset-preact@4.0.2
├── preact@10.4.8
├── preact-cli@3.0.1
├── preact-jsx-chai@3.0.0
├── preact-markup@2.0.0
├── preact-render-to-string@5.1.10
├── preact-router@3.2.1

Tested with Typescript: 4.0.2 / 3.9.7

Expected Behavior

Preact should be able to interlope with components that use typescript and @types/react. There a lot of components like that.

Workaround

This is a very dumb workaround, but it works:

// src/types.d.ts
import React from 'react'

declare global {
    namespace React {
        interface ReactElement {
            nodeName: any
            attributes: any
            children: any
        }
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 20
  • Comments: 17 (7 by maintainers)

Commits related to this issue

Most upvoted comments

May I suggest that this issue is marked as a documentation issue by adding @Svampen fix to the Typescript Configuration section in the docs.

@Svampen suggests above adding the following to tsconfig.json:

  {
    "baseUrl": "./",
    "paths": {
      "react": ["./node_modules/preact/compat/"],
      "react-dom": ["./node_modules/preact/compat/"]
    }

The paths are relative to the baseUrl so if you change that, which you might, you must change the paths accordingly, for example, if you have "baseUrl":"./src" then your paths would start with ../node_modules (double-dot).

You have no control over what and how other imported packages and their dependencies reference and cause @types/react to be installed. Any potential developer who adds @types/react as a dependency instead of a peerDependency in their package.json will cause the same issue. It might be Enzyme or, as it is my case, a dependency of a dependency of react-bootstrap but it might be anything else in the future, you have no control over that.

Whichever way it gets loaded, once it is, you are in trouble. By equating the paths to react and react-dom to preact/compat through tsconfig.json, it doesn’t matter which dependent package imports those definitions, it won’t even look at @types/react even if it does get installed into node_modules, it will simply be ignored.

I had the similar issue in which I’m using a react component in which one of the props allow for a React.ReactElement<any> in which a VNode isn’t matching. After a lot of googling I found one solution that worked for me which was to add the following to my tsconfig.json:

    {
    "baseUrl": "./",
    "paths": {
      "react": ["./node_modules/preact/compat/"],
      "react-dom": ["./node_modules/preact/compat/"]
    }

I was pretty sure that having the following in webpack would do the same (but maybe that wasn’t working in my setup):

resolve: {
      extensions: [".tsx", ".ts", ".js", ".jsx"],
       alias: {
         react: "preact/compat",
         "react-dom": "preact/compat",
       },
    },

This has been added to the dos in https://github.com/preactjs/preact-www/pull/850 thank you @satyam

@marvinhagemeister Right. I posted not to get help, but to point out how @types/react may not have working types in the first place. Therefore, the original issue may not be Preact’s fault, as Preact can’t interop with broken declarations.

@colin-ggd - Would you be able to help me test https://github.com/preactjs/enzyme-adapter-preact-pure/pull/124? It looks to me like all of the type definitions in enzyme-adapter-preact-pure/index.d.ts are in fact only internal types needed when building the package. If so they can simply be excluded.

Edit: Nevermind - it is not ready yet. I rushed this. Some other changes are required.