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

Can't use a Stencil library from a React + TS application #1636

Closed
sslotsky opened this issue Jun 13, 2019 · 24 comments
Closed

Can't use a Stencil library from a React + TS application #1636

sslotsky opened this issue Jun 13, 2019 · 24 comments
Labels
Resolution: Needs Investigation This PR or Issue should be investigated from the Stencil team

Comments

@sslotsky
Copy link

Stencil version:

I'm submitting a:

[x] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/

Current behavior:

When using a Stencil component library from a React + TS application, we receive type errors. Specifically:

TypeScript error in /home/sam/my-marketplace/src/App.tsx(27,9):
Property 'manifold-marketplace' does not exist on type 'JSX.IntrinsicElements'.  TS2339

    25 |       </header>
    26 |       <article>
  > 27 |         <manifold-marketplace />
       |         ^
    28 |       </article>
    29 |     </div>
    30 |   );

Expected behavior:

I should be able to use my web components from a React + TypeScript application without JSX.IntrinsicElements type errors.

Steps to reproduce:

  1. Clone the repo for the React app
  2. Switch to the project directory and start the app with npm i && npm start
  3. Observe the type error both on the page and in the console

Related code:

import React from "react";
import "@manifoldco/ui/dist/manifold/manifold.css";
import { defineCustomElements } from "@manifoldco/ui/dist/loader";
import logo from "./logo.svg";
import "./App.css";

defineCustomElements(window);

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
      <article>
        <manifold-marketplace />
      </article>
    </div>
  );
};

export default App;

Other information:

The React project used to demonstrate the issue is using an alpha release of our component library. You can find that alpha release in this git tag if you wish to download the source code.

The alpha release uses @stencil/[email protected], but we have had this problem with previous versions as well. Before Stencil 1 was released, we had a workaround for the issue, described here under the section labeled "TYPESCRIPT + JSX SETUP". This workaround no longer works.

@ionitron-bot ionitron-bot bot added the triage label Jun 13, 2019
@Nibblesh
Copy link

Nibblesh commented Jun 14, 2019

Similar to your previous workaround, I have something set up for stencil 1+

#1090 (comment)

for convenience I will post again here.

for people struggling with this, I've gotten things working this way:

Edit: Updated thanks to @sslotsky for figuring out the last bits for this solution.

// register-web-components.ts
/* eslint-disable */
import { defineCustomElements, Components, JSX as LocalJSX } from 'stencil-library/dist/loader';
import { DetailedHTMLProps, HTMLAttributes } from 'react';

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

type StencilProps<T> = { [P in keyof T]: Omit<T[P], "ref"> };

type ReactProps<T> = {
  [P in keyof T]: DetailedHTMLProps<HTMLAttributes<T[P]>, T[P]>
};

type StencilToReact<
  T = LocalJSX.IntrinsicElements,
  U = HTMLElementTagNameMap
> = StencilProps<T> & ReactProps<U>;

declare global {
  export namespace JSX {
    interface IntrinsicElements extends StencilToReact {}
  }
}
defineCustomElements(window);
// index.ts
// ...
import './register-web-components';
// ...

eslint will shout about using declare global but this is keeping me going for now. gets auto complete working

@sslotsky
Copy link
Author

@Nibblesh thanks so much for this! Extremely helpful. There is one detail giving me trouble with this solution, though: we are using hooks, and when I use a ref on my web component, it's expecting a callback function instead of a RefObject. Any ideas there?

@Nibblesh
Copy link

@sslotsky could you put an example of what you would like to work? I don't fully understand the scenario without an example

@sslotsky
Copy link
Author

@Nibblesh sure 😄

  const link = React.useRef<HTMLManifoldButtonLinkElement>(null);

  if (link.current) {
    link.current.addEventListener(MANIFOLD_BUTTON_LINK_CLICK, someUrl);
  }

  ...

  // somewhere inside render
        <manifold-button-link ref={link} href={href} preserveEvent>
          Go to all products
        </manifold-button-link>

@sslotsky
Copy link
Author

sslotsky commented Jun 14, 2019

FWIW, I do have something working, but I can't figure out a way to loop through the keys.

type ToReact<T> = DetailedHTMLProps<HTMLAttributes<T>, T>;

declare global {
  export namespace JSX {
    interface IntrinsicElements {
      'manifold-button-link': Components.ManifoldButtonLink &
        ToReact<HTMLManifoldButtonLinkElement>;
    }
  }
}

By doing it this way you are using just the interface defined in Components and extending it with all the React specific stuff. But from what I can tell, there is no mapping from component tag name to the Components namespace, so I need to do this manually for every tag name that I use.

@Nibblesh
Copy link

Maybe this might be usable for you

// register-web-components.ts
import { defineCustomElements, JSX as LocalJSX } from 'stencil-library/dist/loader';
import { HTMLAttributes, DetailedHTMLProps } from 'react';
import { Omit } from 'types';

type StencilToReact<T> = {
  [P in keyof T]?: Omit<DetailedHTMLProps<HTMLAttributes<T[P]>, T[P]>, 'className'> & {
    class?: string;
  } & T[P];
} ;

declare global {
  export namespace JSX {
    interface IntrinsicElements extends StencilToReact<LocalJSX.IntrinsicElements> {
    }
  }
}

defineCustomElements(window)

@sslotsky
Copy link
Author

Not quite, see error below 😄

I think the HTMLManifoldButtonLinkElement bit is important. In the error below we see that we're using ManifoldbuttonLink instead.

src/components/Error/index.tsx:34:10 - error TS2322: Type '{ children: string; ref: RefObject<HTMLManifoldButtonLinkElement>; href: string; preserveEvent: true; }' is not assignable to type 'Pick<DetailedHTMLProps<HTMLAttributes<ManifoldButtonLink>, ManifoldButtonLink>, "hidden" | "dir" | "slot" | "style" | "title" | "color" | "ref" | "key" | "defaultChecked" | ... 244 more ... | "onTransitionEndCapture"> & { ...; } & ManifoldButtonLink'.
  Type '{ children: string; ref: RefObject<HTMLManifoldButtonLinkElement>; href: string; preserveEvent: true; }' is not assignable to type 'Pick<DetailedHTMLProps<HTMLAttributes<ManifoldButtonLink>, ManifoldButtonLink>, "hidden" | "dir" | "slot" | "style" | "title" | "color" | "ref" | "key" | "defaultChecked" | ... 244 more ... | "onTransitionEndCapture">'.
    Types of property 'ref' are incompatible.
      Type 'RefObject<HTMLManifoldButtonLinkElement>' is not assignable to type 'string | ((instance: ManifoldButtonLink | null) => void) | RefObject<ManifoldButtonLink> | null | undefined'.
        Type 'RefObject<HTMLManifoldButtonLinkElement>' is not assignable to type 'RefObject<ManifoldButtonLink>'.
          Type 'HTMLManifoldButtonLinkElement' is not assignable to type 'ManifoldButtonLink'.
            Types of property 'style' are incompatible.
              Type 'CSSStyleDeclaration' is not assignable to type '{ [key: string]: string | undefined; }'.
                Index signature is missing in type 'CSSStyleDeclaration'.

34         <manifold-button-link ref={link} href={href} preserveEvent>

@Nibblesh
Copy link

Nibblesh commented Jun 15, 2019

I won't know until you try it but maybe this

// register-web-components.ts
/* eslint-disable */

import { defineCustomElements, JSX as LocalJSX } from 'stencil-library/dist/loader';
import { HTMLAttributes, DetailedHTMLProps } from 'react';
import { Merge, Omit } from 'ts-essentials';



type StencilToReactElements<T = LocalJSX.IntrinsicElements> = {
  [P in keyof T]?: T[P] & Omit<DetailedHTMLProps<HTMLAttributes<T[P]>, T[P]>, 'className'> & {
    class?: string;
  };
} ;

type StencilToReactRef<T = HTMLElementTagNameMap> = {
  [P in keyof T]: {
    ref?: DetailedHTMLProps<HTMLAttributes<T[P]>, T[P]>['ref']
  }
};

type StencilToReact<T = LocalJSX.IntrinsicElements, U = HTMLElementTagNameMap>= StencilToReactElements<T> & StencilToReactRef<U>;

declare global {
  export namespace JSX {
    interface IntrinsicElements extends StencilToReact {
    }
  }
}

defineCustomElements(window)

@sslotsky
Copy link
Author

This doesn't work either 😞 TypeScript still thinks that ref should be the callback type defined by Stencil:

((elm?: HTMLManifoldButtonLinkElement | undefined) => void)

If it helps @Nibblesh, the component library in question is public, and the version I'm testing against for this issue is available as an alpha release. You could try rendering one of the elements and attaching a ref to it with the useRef hook. If you have time to give that a shot, it would be super helpful! 🙏 I think it would save time in the long run compared to how we're currently approaching it. I really appreciate how much time you've already dedicated to this!

@Nibblesh
Copy link

I gave it a crack this morning without the best luck. Spent ages on this and looking through the type defs as although your issues don't affect me yet I am sure it will be an issue for me at some point.

That being said It might be possible to get this to work using a HoC.

Supply the rest of the props to the web component as per normal, then wrap it in a HoC which clones the child and converts the react ref object into a compatible callback method and return the cloned element.

I don't have the time to give this a try ATM but I'll come back to it when I have a chance.

@sslotsky
Copy link
Author

@Nibblesh I think I got it, modifying your most recent example a bit!

import { Components, JSX as LocalJSX } from '@manifoldco/ui';
import { DetailedHTMLProps, HTMLAttributes } from 'react';

type StencilProps<T> = {
  [P in keyof T]?: Omit<T[P], 'ref'>;
};

type ReactProps<T> = {
  [P in keyof T]?: DetailedHTMLProps<HTMLAttributes<T[P]>, T[P]>;
};

type StencilToReact<T = LocalJSX.IntrinsicElements, U = HTMLElementTagNameMap> = StencilProps<T> &
  ReactProps<U>;

declare global {
  export namespace JSX {
    interface IntrinsicElements extends StencilToReact {}
  }
}

@Nibblesh
Copy link

Nibblesh commented Jun 19, 2019

@sslotsky awesome!! I felt like I was close but I'm glad to hear you figured it out!

I am going to update my original answer with this =)

edit: just to call this one out, it works with typescript 3.5+ but didn't work on the version I had on my project at the time which was 3.3.4. I might have been making it unnecessarily hard for myself to try and figure this out by not upgrading earlier

@sslotsky
Copy link
Author

sslotsky commented Jun 20, 2019

Nice @Nibblesh! We were using 3.5.2, I suppose every one of these discussions should start with environment info 😅

Now, do you happen to know if there's a way for TypeScript to know about the HTML attributes that a Stencil component accepts? It seems like TypeScript allows me to add absolutely any-dasherized-attribute to any custom element. Looking through my Stencil project it seems like those types aren't even listed anywhere. Only their camelCased corollaries are. Am I missing something?

@Nibblesh
Copy link

I think that seems like it should be happening during the compilation step (might be worth writing up a feature request), wherever they're writing out the definitions for the camel case stuff, they could run a regex to convert camel to dash and register the same value there.

@schadenn
Copy link

schadenn commented Sep 2, 2019

Have you tried using Ionics way of doing this?
https://github.com/ionic-team/ionic/blob/master/packages/react/src/components/createComponent.tsx

We're also using Stencil components in a React/Typescript library. I copied an older version of the createComponent.tsx to our codebase some time ago, so I'm not sure the current one is working - but as the Ionic components seem to be working, I suppose their implementation should be fine :)

@adrian-marcelo-gallardo

Thanks @sslotsky and @Nibblesh! In my case this is what worked:

import { applyPolyfills, defineCustomElements, JSX as LocalJSX } from '@wizeline/trip-planner-web-components/dist/loader'
import { DetailedHTMLProps, HTMLAttributes } from 'react';

type StencilProps<T> = {
  [P in keyof T]?: Omit<T[P], 'ref'> | HTMLAttributes<T>;
};

type ReactProps<T> = {
  [P in keyof T]?: DetailedHTMLProps<HTMLAttributes<T[P]>, T[P]>;
};

type StencilToReact<T = LocalJSX.IntrinsicElements, U = HTMLElementTagNameMap> = StencilProps<T> &
  ReactProps<U>;

declare global {
  export namespace JSX {
    interface IntrinsicElements extends StencilToReact { }
  }
}

I needed to add | HTMLAttributes<T> to StencilProps; if not I got issues if I wanted to assign an id or other attributes.

If anyone has a problem with other unsupported HTML attribute you can also assign AllHTMLAttributes instead (that will allow other attributes like name or src; not sure if useful though)

@jagreehal
Copy link

I set up a monorepo to experiment Stencil and React integration using the info in this thread. See https://github.com/jagreehal/react-stencil-dx. When I get time this week I going to follow up on the suggestion by @schadenn

@serchavalos
Copy link

@sslotsky another happy dev :-) thanks for the tip!

@peterpeterparker
Copy link
Contributor

Thx @sslotsky and @adrian-marcelo-gallardo, same to me in an Ionic React app!

import { applyPolyfills, defineCustomElements, JSX as LocalJSX } from '@deckdeckgo/lazy-img/dist/loader'
import { DetailedHTMLProps, HTMLAttributes } from 'react';

type StencilProps<T> = {
    [P in keyof T]?: Omit<T[P], 'ref'> | HTMLAttributes<T>;
};

type ReactProps<T> = {
    [P in keyof T]?: DetailedHTMLProps<HTMLAttributes<T[P]>, T[P]>;
};

type StencilToReact<T = LocalJSX.IntrinsicElements, U = HTMLElementTagNameMap> = StencilProps<T> &
    ReactProps<U>;

declare global {
    // eslint-disable-next-line @typescript-eslint/no-namespace
    export namespace JSX {
        interface IntrinsicElements extends StencilToReact { }
    }
}

applyPolyfills().then(() => {
    defineCustomElements(window);
});

@mlp73
Copy link

mlp73 commented Jan 9, 2020

Maybe there is something in the bigger picture that I don't get. But why cannot generated types simply have an export of interface IntrinsicElements. The types are generated correctly and ready for use but simply not exported. Why?

Example. If components.d.ts only could have an export in front of interface IntrinsicElements:

//components.d.ts
`declare namespace LocalJSX {
interface MyComponent {
'first'?: string;
'last'?: string;
'middle'?: string;
}

export interface IntrinsicElements {
'my-component': MyComponent;
}
}`

// dev.d.ts in my react project
`import { JSX as LocalJSX } from '@mypackage/webcomp/types/components';

declare global {
namespace JSX {
interface IntrinsicElements extends LocalJSX.IntrinsicElements {}
}
}`

@ionitron-bot ionitron-bot bot added the ionitron: stale issue This issue has not seen any activity for a long period of time label Feb 8, 2020
@ionitron-bot
Copy link

ionitron-bot bot commented Feb 8, 2020

Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Stencil, please create a new issue and ensure the template is fully filled out.

Thank you for using Stencil!

@ionitron-bot ionitron-bot bot closed this as completed Feb 8, 2020
@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Feb 8, 2020
@claviska
Copy link
Contributor

Reopening since this is still an issue as mentioned in #2729.

@claviska claviska reopened this Nov 18, 2020
@claviska claviska added bug and removed ionitron: stale issue This issue has not seen any activity for a long period of time labels Nov 18, 2020
@ionitron-bot ionitron-bot bot added the ionitron: stale issue This issue has not seen any activity for a long period of time label Dec 18, 2020
@splitinfinities
Copy link
Contributor

Hey everyone, first of all, thank you so much for the patience. I want to better understand where this issue is at. Is the original repro case still effective? Or, can we get an updated repro case going to I can better understand the behavior here on Stencil v2 code?

@splitinfinities splitinfinities added Awaiting Reply This PR or Issue needs a reply from the original reporter. Repro: Yes and removed ionitron: stale issue This issue has not seen any activity for a long period of time labels Aug 3, 2021
@ionitron-bot ionitron-bot bot added the ionitron: stale issue This issue has not seen any activity for a long period of time label Sep 2, 2021
@rwaskiewicz rwaskiewicz removed the ionitron: stale issue This issue has not seen any activity for a long period of time label Sep 3, 2021
@rwaskiewicz rwaskiewicz added Resolution: Needs Investigation This PR or Issue should be investigated from the Stencil team and removed Feature: TS Typings labels Mar 25, 2022
@rwaskiewicz
Copy link
Contributor

Hey there 👋

I apologize it took so long for someone on the team to acknowledge this issue. At this time, Stencil v1 and lower is no longer supported, and the original reproduction case is no longer hosted on GitHub. As a result, I'm going to close this issue.

If this issue is still occurring in Stencil v4 and the latest version of the Stencil-React output target, can you please create a new issue for us with a minimal reproduction case? Thanks!

@ionitron-bot ionitron-bot bot removed the Awaiting Reply This PR or Issue needs a reply from the original reporter. label Jul 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Needs Investigation This PR or Issue should be investigated from the Stencil team
Projects
None yet
Development

No branches or pull requests