Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[typescript] React.ReactElement type is not compatible with Preact and prevents interoperability with React #2748

Closed
VanTanev opened this issue Sep 8, 2020 · 17 comments
Labels
8.x Related to 8.x or prior versions

Comments

@VanTanev
Copy link

VanTanev commented Sep 8, 2020

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
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]

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
        }
    }
}
@colin-ggd
Copy link

colin-ggd commented Dec 1, 2020

From what I can tell, this is being caused by this file - https://github.com/preactjs/enzyme-adapter-preact-pure/blob/master/index.d.ts

A quick fix would be to make the "nodeName", "attributes", and "children" optional.

Looking for a more legit fix, but thanks for the workaround!

@marvinhagemeister marvinhagemeister added the 8.x Related to 8.x or prior versions label Dec 1, 2020
@marvinhagemeister
Copy link
Member

FYI: We don't have nodeName, attributes and children on our vnode shape anymore since Preact 10.0.0

@colin-ggd
Copy link

colin-ggd commented Dec 1, 2020

@marvinhagemeister I should have mentioned, this is happening with Preact v10.3.1, and is being caused by the enzyme-adapter-preact-pure dependency.

To reproduce (with preact-cli 3.0.3)

preact create typescript example-project
cd example-project
npm install react-bootstrap
# Add the following to app.tsx
# import { Button } from "react-bootstrap";
# Then in the render function add this - 
# <Button>This breaks</Button>

# Also, for react-bootstrap, add "skipLibCheck": true to tsconfig (due to different versions of @types/react, a non issue for the preact error. 
yarn build

You can also use OPs method for reproducing, this was just the way I ran into this bug.

The issue is due to the fact that the enzyme-adapter-preact-pure is overwriting the preact interface definition for VNode. If I comment it out / remove it, then preact starts working correctly.

@marvinhagemeister
Copy link
Member

cc @robertknight who maintains our enzyme adapter

@colin-ggd
Copy link

@marvinhagemeister @robertknight Bump. This is still causing issues.

robertknight added a commit to preactjs/enzyme-adapter-preact-pure that referenced this issue Dec 7, 2020
The types defined in `index.d.ts` are only for use by the package's
internal code and tests. There is nothing there that should be
published.

See preactjs/preact#2748
@robertknight
Copy link
Member

robertknight commented Dec 7, 2020

@colin-ggd - Would you be able to help me test preactjs/enzyme-adapter-preact-pure#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.

robertknight added a commit to preactjs/enzyme-adapter-preact-pure that referenced this issue Dec 7, 2020
Move Preact 8 VNode extensions from `index.d.ts` to `internal.d.ts` to
avoid shipping them with the package and causing type checking errors
for Preact 10 users.

See preactjs/preact#2748
robertknight added a commit to preactjs/enzyme-adapter-preact-pure that referenced this issue Dec 7, 2020
Remove Preact 8 VNode extensions from `index.d.ts` as these cause type
checking errors for Preact 10 users.

See preactjs/preact#2748
@alubchuk
Copy link

alubchuk commented Dec 9, 2020

@robertknight I've also experienced this issue today, but since I don't use enzyme I just completely removed it from the preact typescript project. I wonder why the typescript template defaults to enzyme for unit testing components?

@rschristian
Copy link
Member

I wonder why the typescript template defaults to enzyme for unit testing components?

All of the templates should be using enzyme for unit testing, not just TS. Is there something else you think it should be defaulting to? Enzyme is quite a nice test framework and I certainly prefer it to the rest.

@simonv3
Copy link

simonv3 commented Jan 28, 2021

@rschristian I'd opt for Testing Library, as it's the first listed in the docs.

@rschristian
Copy link
Member

@simonv3 Gotcha, makes sense. Maybe at some point the templates will be converted then.

@gilbert
Copy link

gilbert commented Feb 3, 2021

This may not be Preact's issue.

I'm working on a React project and I'm getting a very similar error. Here's a minimal example:

const Foo: React.FC = () => <p></p>

function Bar() {
  return <Foo />  /* Type error! */
}

This gives the following TS error:

'Foo' 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': tag, attrs, statets(2786)

No idea what's causing this. I'm using React 17. Would be great to have a workaround.

@marvinhagemeister
Copy link
Member

@gilbert that is it related to Preact. Can you file that issue in the React repo instead? This issue here is about interoperability between React and Preact typings and it sounds like you're not using Preact in the first place.

@gilbert
Copy link

gilbert commented Feb 3, 2021

@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.

@dionjwa
Copy link

dionjwa commented Jul 25, 2021

I'm still getting this with @types/react@16 and @types/react@17, [email protected], [email protected]. Is there any other workaround apart from the original poster? That doesn't work for me.

@Svampen
Copy link

Svampen commented Aug 31, 2021

I had the similar issue in which I'm using a react component in which one of the props allow for a React.ReactElement 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",
       },
    },

@Satyam
Copy link

Satyam commented May 10, 2022

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.

@JoviDeCroock
Copy link
Member

JoviDeCroock commented Jun 3, 2022

This has been added to the dos in preactjs/preact-www#850 thank you @Satyam

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
8.x Related to 8.x or prior versions
Projects
None yet
Development

No branches or pull requests