Vue-cli: Using props in tsx is not possible

Created on 31 Aug 2018  路  22Comments  路  Source: vuejs/vue-cli

Version

3.0.1

Reproduction link

https://github.com/DCzajkowski/vue-jsx-reproduction

Node and OS info

Node v9.11.2 / yarn 1.9.4 / macOS

Steps to reproduce

$ yarn && yarn serve

What is expected?

Compilation successful.

What is actually happening?

An error appears

ERROR in /Users/Darek/web/sites/test2-vue-project/src/views/Home.tsx
9:21 Property 'msg' does not exist on type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<string, any>> | ThisTypedComponentOptionsWithArrayProps<Vue, object, object, object, never> | ThisTypedComponentOptionsWithRecordProps<...> | undefined'.
     7 |     return (
     8 |       <div class='home'>
  >  9 |         <HelloWorld msg='Welcome to Your Vue.js   TypeScript App' />
       |                     ^
    10 |       </div>
    11 |     )
    12 |   }
No lint errors found
Version: typescript 3.0.3, tslint 5.11.0
Time: 9742ms

I am very sorry if this is a set-up problem on my side or if it's not a correct repo to report this kind of stuff. I don't have experience with TSX and really can't find a solution to why this may not work. Sorry if it's a duplicate.

P.S. is https://github.com/vuejs/vue/pull/6856 related?

discussion typescript

Most helpful comment

I got the same here.

Just created a component

import { Component, Vue, Prop } from 'vue-property-decorator';

@Component()
export default class NavLink extends Vue {

    @Prop()
    public widget!: string;

    @Prop({ default: '#' })
    public href!: string;

    @Prop()
    public toggle!: string;

    protected render(h: any) {
        return (
                <a class='nav-link' data-widget={this.widget} href={this.href} data-toggle={this.toggle}>
                    {this.$slots.default}
                </a>
        );
    }
}

And tried to use it in another one

import { Component, Vue } from 'vue-property-decorator';
import NavLink from '@/components/core/main-header/nav-link/nav-link';

@Component({
    components: {
        'nav-link': NavLink
    },
    name: 'main-header'
})
export default class MainHeader extends Vue {

    public render(h: any) {
        return (
            <NavLink widget='pushmenu'>
                <i class='fa fa-bars'></i>
            </NavLink>
        );
    }
}

Than I got the error

Property 'widget' does not exist on type 'ThisTypedComponentOptionsWithArrayProps<Vue, object, object, object, never> | ThisTypedComponentOptionsWithRecordProps<Vue, object, object, object, object> | ComponentOptions<...> | undefined'.

If instead of the <NavLink widget='pushmenu'> I use <nav-link widget='pushmenu'> it works fine. Or, if the attribute was something-widget instead of just widget, it works fine too.

All 22 comments

I got the same here.

Just created a component

import { Component, Vue, Prop } from 'vue-property-decorator';

@Component()
export default class NavLink extends Vue {

    @Prop()
    public widget!: string;

    @Prop({ default: '#' })
    public href!: string;

    @Prop()
    public toggle!: string;

    protected render(h: any) {
        return (
                <a class='nav-link' data-widget={this.widget} href={this.href} data-toggle={this.toggle}>
                    {this.$slots.default}
                </a>
        );
    }
}

And tried to use it in another one

import { Component, Vue } from 'vue-property-decorator';
import NavLink from '@/components/core/main-header/nav-link/nav-link';

@Component({
    components: {
        'nav-link': NavLink
    },
    name: 'main-header'
})
export default class MainHeader extends Vue {

    public render(h: any) {
        return (
            <NavLink widget='pushmenu'>
                <i class='fa fa-bars'></i>
            </NavLink>
        );
    }
}

Than I got the error

Property 'widget' does not exist on type 'ThisTypedComponentOptionsWithArrayProps<Vue, object, object, object, never> | ThisTypedComponentOptionsWithRecordProps<Vue, object, object, object, object> | ComponentOptions<...> | undefined'.

If instead of the <NavLink widget='pushmenu'> I use <nav-link widget='pushmenu'> it works fine. Or, if the attribute was something-widget instead of just widget, it works fine too.

@Leandro-Albano your solution does not provide valid type checking for props. Bump the topic

same problem for me

@Akryum OP has already provided a reproduction link.
If it's out-of-date or so, I just made another one: vue-tsx

you can reproduce by npm install npm run serve and then inpsect the typescript type-check error (the code compiles and works as expected, but the tsc emits an error and so does the VSCode)
image

Same problem for me...is there any workaround that would keep the type-checking?

Same here

@Akryum @yyx990803 Can we please get any update on that?

I have just cloned my repo, ran a fresh yarn and what I've noticed is that:

  • it works in the browser
  • error is thrown in the console, but it doesn't seem to mean anything (the in-browser code works)
  • the code is continuously being rebuilt, even though there are no changes
  • changing private to public does not affect the above behaviour

I've managed to resolve the problem for my scenario using the following pattern
[sorry if the guide is a bit longer and not suitable for github, but might help some people having the same problem]

First of all I've extended the JSX.ElementAttributesProperty interface and created an abstract Vue extension class to serve as a base for the component. So let's create a ts file [e.g. vue-tsx.ts] somewhere in your project and place the following in there

import Vue from "vue";

export default abstract class TsxComponent<P> extends Vue {
    private vueTsxProps: Readonly<{}> & Readonly<P>;
}

declare global {
    namespace JSX {
        interface ElementAttributesProperty { vueTsxProps: {}; }
    }
}

Now the component has to be created using following pattern - It's essential that there's an interface defined and implemented by the component - ensures the type-safety. This is passed to the TsxComponent base class as a generic argument which creates the dummy vueTsxProps property (see the abstract class above), which is served to the TS typechecker as the valid parameter list. So let's create a sample component like this

import TsxComponent from "../../vuetsx"; //this is the relative path to the file created above
import { Component, Prop } from 'vue-property-decorator'

interface PersonDataArgs {
    username: string;
    age: number
}

@Component
export default class MyComponent extends TsxComponent<PersonDataArgs> implements PersonDataArgs {
    @Prop() username!: string;
    @Prop() age!: number;

    render() {
        return <span>Hello {this.username}, you are supposed to be {this.age} old</span>
    }
}

Now when using the component, correct type-checking is applied [1st one denied due to missing mandatory property, 2nd one ok, 3rd one denied due to unknown property applied]

image

@brunolau It's a nice hack, but it forces you to write types and prop declarations twice, which doesn't seem like a good idea :/

Yes, that's the downside of the approach. However you are still protected by the compiler type checking. As you are both passing the interface type to the base class as well as implementing it on the component class, you are getting protected from making a typo on either side

Let's figure you want to add property "birthYear: number" to the component. If you forget to add it to the interface, you will get compile error from the TSX template and if you forget to add it to the component class, you will get compile error stating that the component class doesn't implement the interface.

Still, I fully agree that having to write the property twice isn't the ideal approach, however I wasn't able to find any other way besides not using the property decorators. [If you want to stick with props: {} annotation rather than property decorators, just change the "vueTsxProps" to "props" from my example and you should be good. There are also some other ways for property decorators, these will however make all the component properties optional as well as include all Vue base class properties in the intellisense, for more info see This issue on Typescript repo

As I wanted to stick with property decorators, I've decided to gowith the approach described above and I'm affraid that until the Vue version 3 is out, one will always have to choose the "smaller evil"

@manneredboor ,

That was not a solution, I'm just giving as much info as I have about the issue. Maybe I should say that it compiles fine, not that it works fine.

I have been using this library to great success, you can get typechecking for both props and events.
https://github.com/wonderful-panda/vue-tsx-support

There is also a vue-cli-plugin to configure it for you.

import * as tsx from 'vue-tsx-support'

const Foo = tsx.component({
  name: 'Foo',

  props: {
    msg: {
      type: String,
      required: true as true
    }
  },

  render() {
    return (
      <span>Hi</span>
    )
  }
})

const Test = {
  name: 'Test',

  components: {
    Foo
  },

  render() {
    return (
      <div>
        <Foo  /> // ERROR since you did not provide the props
      </div>
    )
  }
}

"vue": "^2.5.17",
"typescript": "^3.0.0",
"vue-class-component": "^6.0.0",

Me too~~
Property 'data' is only alternative,Hope it can be solved.

wx20181210-234936 2x

I'm not sure that we should solve this within vue-cli.

The state of TSX and Vue 2.* simply is what we see here: without using a lib like vue-tsx-support, TSX won't work reliably. (sidenote: TSX should work out of the box with Vue 3.)

Seeing as that lib also has a cli-plugin already, I'm not sure we can do much in CLI core to further improve the sitution.

CommentS?

Thank you for this answer. I was not aware of vue-tsx-support nor its cli. I'll investigate. Thanks!

I agree with @LinusBorg ... until Vue 3 hits, TSX can be supported by a third party lib. Although I think when Vue 3 does come out, TS support should be a first class citizen (including TSX) in Vue CLI.

@DCzajkowski vue-tsx-support does not have a cli itself, but if you are using vue cli, you can run vue add vue-tsx-support and it should configure it for you. You need to have selected the babel option when you set up your app using CLI (or add it in, however you do that), or you may have problems.

I'll close this then. Nothing further to do.

you can run vue add vue-tsx-support and it should configure it for you.

It's vue add tsx-support, just in case someone wonders, why they get an 404 :)

@Akryum OP has already provided a reproduction link.
If it's out-of-date or so, I just made another one: vue-tsx

you can reproduce by npm install npm run serve and then inpsect the typescript type-check error (the code compiles and works as expected, but the tsc emits an error and so does the VSCode)
image

For me the work around was by updating the shims-tsx.d.ts file

import Vue, { VNode } from 'vue';

declare global {
  namespace JSX {
    // tslint:disable no-empty-interface
    interface Element extends VNode {}
    // tslint:disable no-empty-interface
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [elem: string]: any;
    }

    // ** Add the following lines to solve the issue **
    interface ElementAttributesProperty{
      $props: {}
    }
  }
}

I know its after a very long time. Just incase someone also fetches the same issue.

It makes me crazy to set up a project with typescript and jsx. From now, I should use React if wanna work with jsx. Too many problems

I got the same here.

Just created a component

import { Component, Vue, Prop } from 'vue-property-decorator';

@Component()
export default class NavLink extends Vue {

    @Prop()
    public widget!: string;

    @Prop({ default: '#' })
    public href!: string;

    @Prop()
    public toggle!: string;

    protected render(h: any) {
        return (
                <a class='nav-link' data-widget={this.widget} href={this.href} data-toggle={this.toggle}>
                    {this.$slots.default}
                </a>
        );
    }
}

And tried to use it in another one

import { Component, Vue } from 'vue-property-decorator';
import NavLink from '@/components/core/main-header/nav-link/nav-link';

@Component({
    components: {
        'nav-link': NavLink
    },
    name: 'main-header'
})
export default class MainHeader extends Vue {

    public render(h: any) {
        return (
            <NavLink widget='pushmenu'>
                <i class='fa fa-bars'></i>
            </NavLink>
        );
    }
}

Than I got the error

Property 'widget' does not exist on type 'ThisTypedComponentOptionsWithArrayProps<Vue, object, object, object, never> | ThisTypedComponentOptionsWithRecordProps<Vue, object, object, object, object> | ComponentOptions<...> | undefined'.

If instead of the <NavLink widget='pushmenu'> I use <nav-link widget='pushmenu'> it works fine. Or, if the attribute was something-widget instead of just widget, it works fine too.

I donot know why, but it works.馃槀

Was this page helpful?
0 / 5 - 0 ratings

Related issues

brunoseco picture brunoseco  路  35Comments

yyx990803 picture yyx990803  路  34Comments

lbicknese picture lbicknese  路  41Comments

yyx990803 picture yyx990803  路  80Comments

xrei picture xrei  路  40Comments