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] Typing a variable holding a component as value #486

Closed
glecetre opened this issue Aug 26, 2020 · 27 comments
Closed

[TypeScript] Typing a variable holding a component as value #486

glecetre opened this issue Aug 26, 2020 · 27 comments
Labels
bug Something isn't working Fixed Fixed in master branch. Pending production release.

Comments

@glecetre
Copy link

When using TypeScript, it seems impossible to use a component as a type.
For instance, it is not possible to type a store to hold a component instance like so:

import AnyComponent from "./AnyComponent"

const componentStore = writable<SvelteComponent>(null)

$componentStore = AnyComponent

The last line will give the following error:

Type 'typeof <component>__SvelteComponent_' is missing the following properties from type 'SvelteComponentDev': $set, $on, $destroy, $capture_state, and 2 more.

Maybe it is me that's missed something about how components are typed, but I would like to be able to use that kind of pattern.

Here is a StackOverflow post I opened before writing this issue, where @dummdidumm instructed me to open this issue. I hope that's ok.

@dummdidumm dummdidumm added the bug Something isn't working label Aug 26, 2020
@dummdidumm
Copy link
Member

Hi there 😄
One thing is that we are missing some properties/methods on our internal definition of our Svelte component class. The other thing is that the typing is wrong, it should be writable<typeof SvelteComponent>, else you are refering to the instance of that component, not the class type.

dummdidumm added a commit that referenced this issue Aug 30, 2020
with that of SvelteComponentDef
#486
@dummdidumm dummdidumm added the Fixed Fixed in master branch. Pending production release. label Aug 30, 2020
@mckravchyk
Copy link

mckravchyk commented Mar 16, 2021

I have encountered a similar issue.

import type { SvelteComponent } from 'svelte';
import Comp from './Comp.svelte';

const comp: SvelteComponent = Comp; // Type 'typeof SvelteComponentDev' is missing the following properties from type SvelteComponentDev: $set, $on, $destroy ...

v 3.35.0

The question is, why an imported component has a type "typeof SvelteComponentDev" rather than SvelteComponentDev

@dummdidumm
Copy link
Member

Because Comp is the class, not the instance of that class.

class Foo {
    bar: string;
}

class Comp extends Foo {}

const comp: Foo = new Comp(); // OK
const compClass1: Foo = Comp; // WRONG
const compClass2: typeof Foo = Comp; // OK

@mckravchyk
Copy link

Ahh. Makes sense. Thank you.

@Alfagun74
Copy link

Alfagun74 commented Apr 1, 2021

Fighting with this for 2 days straight now. I just don't get it:

<script lang="ts">
  import MyComponent from "./MyComponent.svelte";
  let myComponent: typeof MyComponent;
</script>

<MyComponent bind:this={myComponent}/> //Error here
Argument of type 'typeof MyComponent__SvelteComponent_' is not assignable to parameter of type 'MyComponent__SvelteComponent_'.   
Type 'typeof MyComponent__SvelteComponent_' is missing the following properties from type 'MyComponent__SvelteComponent_': $$prop_def, $$events_def, $$slot_def, $on, and 5 more.ts(2345)

What am i doing wrong here? I just want to get that Instance into a variable. 😭

@jasonlyu123
Copy link
Member

jasonlyu123 commented Apr 1, 2021

@Alfagun74 the issue you are facing is not the same as others in this thread. What you want is an instance of the class. So you just need:

<script lang="ts">
  import MyComponent from "./MyComponent.svelte";
  let myComponent: MyComponent;
</script>

<MyComponent bind:this={myComponent}/>

Other people want a dynamic component so they need the class itself, a special function.

@glics
Copy link

glics commented Jun 26, 2021

I am a little confused about this situation: Let's say I want to have a property that can be any component, to be used in an array.
Here is a real world example of what I mean, using phosphor-svelte:

phosphor-svelte/lib/index.d.ts:

[...]
export declare class CheckCircle extends SvelteComponent<IconProps> {}
export declare class CheckSquare extends SvelteComponent<IconProps> {}
[...]

mycomponent.svelte

<script lang="ts">
  import { CheckCircle, CheckSquare } from 'phosphor-svelte/lib';

  interface ItemPanel {
    icon: any;
    text: string;
  }

  const items: ItemPanel[] = [
    {
      icon: CheckCircle,
      text: 'Lorem Ipsum'
    },
    {
      icon: CheckSquare,
      text: 'Foo Bar'
    }
  ];
</script>
{#each items as item}
  <svelte:component this={item.icon} />
  <h2>{item.text}</h2>
{/each}

What type would you give to icon? My guess was

  interface ItemPanel {
    icon: typeof SvelteComponent;
    text: string;
  }

but vscode.typescript-language-features is yelling at me that

Types of construct signatures are incompatible.
  Type 'new (options: IComponentOptions) => CheckCircle' is not assignable to type 'new <Props = {}>(options: IComponentOptions) => SvelteComponent<Props>'.
    Construct signature return types 'CheckCircle' and 'SvelteComponent<Props>' are incompatible.
      The types of '$$prop_def' are incompatible between these types.
        Type 'IconProps' is not assignable to type 'Props'.
          'Props' could be instantiated with an arbitrary type which could be unrelated to 'IconProps'.

I was wondering why it was wrong, and while messing around I tried to replace the interface with a type alias:

  type ItemPanel = {
    icon: typeof SvelteComponent;
    text: string;
  };

...and this one just works. I am relatively new to TS so maybe it's better to use types rather than interfaces for this kind of task, but I still don't understand why it gives me an error in an interface. I think I am going a bit out of scope here, but hopefully this can help anyone who has the same problem in the future.


EDIT: I figured it out, and of course it's my bad... Whoops.
When using the interface I imported

import type { SvelteComponent } from 'phosphor-svelte/lib/shared';

instead of

import type { SvelteComponent } from 'svelte';

That's what actually fixed it.

@dummdidumm
Copy link
Member

I don't know why it works with type and not interface, but instead of typeof SvelteComponent write new (...args: any[]) => SvelteComponent

@glics
Copy link

glics commented Jun 26, 2021

Thanks, but seems that with the right import typeof SvelteComponent works as well.
Am I supposed to use new (...args: any[]) => SvelteComponent rather than typeof SvelteComponent though, and why? Just curious on which one is the better practice

@fourglobe302500
Copy link

Hi I am having a similar issue
I am using a library that cretes modals for me, and in one of the function used receives a component as a argument like this:
dialogs.modal(SaveSelection, { selected: getSaveId($save) });
the object contains the props.
but it gives me this error:
Argument of type 'typeof SaveSelection__SvelteComponent_' is not assignable to parameter of type 'string | SvelteComponentDev | DialogOptions'. Type 'typeof SaveSelection__SvelteComponent_' is missing the following properties from type 'SvelteComponentDev': $set, $on, $destroy, $$prop_def, and 5 more.
can someone help me.

@aradalvand
Copy link

@fourglobe302500
Can you share the whole code and also link to the library you're using?

@fourglobe302500
Copy link

@fourglobe302500
Copy link

the file with the component is the components\SaveSelection.svelte
and the file calling the function is the routes\index.svelte

@fourglobe302500
Copy link

The lib is the svelte dialogs

@aradalvand
Copy link

aradalvand commented Apr 18, 2022

Not sure about why this error is occurring in this particular context, I think you should create an issue in the svelte-dialogs repo.
That being said, I have a modal component with typing support and the way the typings are defined is as follows:

import type { SvelteComponentTyped } from 'svelte';
import type { Newable } from 'ts-essentials';
import SomeComp from './SomeComp.svelte';

type ComponentProps<T extends SvelteComponentTyped> =
	T extends SvelteComponentTyped<infer R> ? R : unknown;

function openModal(open<Comp extends SvelteComponentTyped>(component: Newable<Comp>, props?: ComponentProps<Comp>) {
    // ...
}

openModal(SomeComp, {
    // The props are detected and you get code-completion, etc.
});

@fourglobe302500
Copy link

Thanks @aradalvand I will try it later and open the issue.

@fourglobe302500
Copy link

@aradalvand found the problem though is a sneaky one

Before my components was

<DialogContent>
  <h1>Title</h1>
  <div>
    \\ ..-
  </div>
</DialogContent>

But I was missing the slot parameter on the component

<DialogContent>
  <h1 slot="header">Title</h1>
  <div slot="body">
    \\ ..-
  </div>
</DialogContent>

I only found it because I reread the documentation but this time on github that has the dark theme, npm deosn't have it

@opensas
Copy link
Contributor

opensas commented Jan 5, 2023

I couldn't find a way to type an async function that returns a Svelte component.

I tried with this:

<script lang="ts">
  import type { SvelteComponent } from 'svelte';
  export let icon: () => Promise<typeof SvelteComponent>;
</script>

But when I call it like this:

<TextIcon icon={() => import('$lib/icons/Email.svelte')}>text</TextIcon>

I get

Type 'Promise<typeof import("/home/sas/devel/apps/glas-it/apps/wingback/billing/src/lib/icons/Email.svelte")>' is not assignable to type 'Promise<typeof SvelteComponentDev>'.
  
Property 'prototype' is missing in type 'typeof import("/home/sas/devel/apps/glas-it/apps/wingback/billing/src/lib/icons/Email.svelte")' but required in type 'typeof SvelteComponentDev'.ts(2322)

Any idea?

Here's a question at SO with the same issue


Edit: someone answered at SO, I just had to type it like this

<script lang="ts">
  import type { ComponentType } from 'svelte';
  export let icon: () => Promise<{ default: ComponentType }>;
</script>

@doceazedo
Copy link

This is still randomly happening to me. I just created a new project using Vite and Svelte 4.0.0-next.0 with these components:

<!-- App.svelte -->
<script>
  import Example from "./lib/Example.svelte";
  import Smile from "./lib/Smile.svelte";
</script>

<Example icon={Smile} />
<!-- Example.svelte -->
<script lang="ts">
  import { SvelteComponent } from "svelte";

  export let icon: typeof SvelteComponent;
</script>

<svelte:component this={icon} />
<!-- Smile.svelte -->
😊

And I'm still getting this error:

// <Example icon={Smile} />
//          ^

Type 'typeof Smile__SvelteComponent_' is not assignable to type 'typeof SvelteComponentDev'.
  Types of construct signatures are incompatible.
    Type 'new (options: ComponentConstructorOptions<{ [x: string]: never; }>) => Smile__SvelteComponent_' is not assignable to type 'new <Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any>(options: ComponentConstructorOptions<Props>) => SvelteComponentDev<...>'.
      Types of parameters 'options' and 'options' are incompatible.
        Type 'ComponentConstructorOptions<Props>' is not assignable to type 'ComponentConstructorOptions<{ [x: string]: never; }>'.
          Types of property 'props' are incompatible.
            Type 'Props' is not assignable to type '{ [x: string]: never; }'.
              Type 'Record<string, any>' is not assignable to type '{ [x: string]: never; }'.
                'string' index signatures are incompatible.
                  Type 'any' is not assignable to type 'never'.

If I change export let icon: typeof SvelteComponent; to export let icon: typeof SvelteComponent<any>; I get this error instead:

// <Example icon={Smile} />
//          ^

Types of construct signatures are incompatible.
  Type 'new (options: ComponentConstructorOptions<{ [x: string]: never; }>) => Smile__SvelteComponent_' is not assignable to type 'new (options: ComponentConstructorOptions<any>) => SvelteComponentDev<any, any, any>'.
    Type 'Smile__SvelteComponent_' is not assignable to type 'SvelteComponentDev<any, any, any>'.
      Property '$$set' is optional in type 'Smile__SvelteComponent_' but required in type 'SvelteComponentDev<any, any, any>'.

@eugbyte
Copy link

eugbyte commented Jul 12, 2023

For anyone struggling to type bindings of component instances:

For some strange reason, Svelte Components we create ourselves is missing the prototype property, but required in SvelteComponent.

I got the type to work with the Typescript Omit utility type.

<script lang="ts">
        import type { SvelteComponent } from "svelte";
        import { SearchBar } from '$lib/components';
        
	let searchbar: Omit<typeof SvelteComponent, "prototype">;
</script>

<SearchBar bind:this={searchbar}/>

@jasonlyu123
Copy link
Member

jasonlyu123 commented Jul 12, 2023

let searchbar: Omit<typeof SvelteComponent, "prototype">;

No. This is not correct. typeof SvelteComponent is the type of the constructor of the class, but what you want is the instance of it. See my previous comment
#486 (comment)

@eugbyte
Copy link

eugbyte commented Jul 12, 2023

let searchbar: Omit<typeof SvelteComponent, "prototype">;

No. This is not correct. typeof SvelteComponent is the type of the constructor of the class, but what you want is the instance of it. See my previous comment #486 (comment)

Ok, I understand now, thanks

@sulitprod
Copy link

export let type: new (options: ComponentConstructorOptions) => SvelteComponent

This code work for me

@SarcevicAntonio
Copy link
Member

SarcevicAntonio commented Sep 7, 2023

Commenting this for visibility:

TypeScript wasn't happy with typeof SvelteComponent resulting in the following Error:
Argument of type 'typeof MySpecificComponent__SvelteComponent_' is not assignable to parameter of type 'typeof SvelteComponent'.

Using ComponentType instead works like a charm, tho.

@themajashurka
Copy link

themajashurka commented Sep 21, 2023

I use this type to get type info of component methods for example (as an extension to the previous answer):

import Cmp from './cmp.svelte'

let cmp: InstanceType<ComponentType<Cmp>>

@MrBns
Copy link

MrBns commented Oct 10, 2023

Commenting this for visibility:

TypeScript wasn't happy with typeof SvelteComponent resulting in the following Error: Argument of type 'typeof MySpecificComponent__SvelteComponent_' is not assignable to parameter of type 'typeof SvelteComponent'.

Using ComponentType instead works like a charm, tho.

it solves my problem. thanks a lot.

@Bishwas-py
Copy link

Commenting this for visibility:

TypeScript wasn't happy with typeof SvelteComponent resulting in the following Error: Argument of type 'typeof MySpecificComponent__SvelteComponent_' is not assignable to parameter of type 'typeof SvelteComponent'.

Using ComponentType instead works like a charm, tho.

Thanks, export let component: ComponentType; worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Fixed Fixed in master branch. Pending production release.
Projects
None yet
Development

No branches or pull requests