Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VueJS 3 Composition API and TypeScript type issue when passing props into setup: Property 'user' does not exist on type

I am struggling to work out what I am doing wrong to get TypeScript to understand that props.user is of type UserInterface, any advice or pointers would be amazing

[email protected], [email protected], [email protected]. This feels more like a native VueJS or TypeScript issue, rather than anything to do with Quasar

UserInterface for reference:

export default interface UserInterface {
  id: number,
  email: string,
  name: string,
  agent_id: string
}

Component:

<template>
  <q-avatar :color="color" :text-color="textColor" :size="size" :title="user.name" style="outline: 2px solid #ffffff">
    {{ initials(user.name) }}
  </q-avatar>
</template>

<script lang="ts">
import UserInterface from 'logic/interfaces/UserInterface'
import {computed, defineComponent, PropType} from 'vue'

const colors: Record<string, string> = {
  A: 'blue',
  K: 'black',
  R: 'purple',
  S: 'primary'
}

export default defineComponent({
  name: 'UserIcon',
  props: {
    user: {
      type: Object as PropType<UserInterface>,
      required: true
    },
    size: {
      type: String,
      required: false,
      default: 'lg',
      validator: function (value: string) {
        return ['xs', 'sm', 'md', 'lg', 'xl'].indexOf(value) !== -1
      }
    },
    textColor: {
      type: String,
      required: false,
      default: 'white'
    }
  },
  setup (props) {
    const initial = props.user.agent_id.charAt(0)
    const color = computed(() => {
      return colors[initial] || 'green'
    })

    return {
      color,
      initials (name: string) {
        const names = name.split(' ')
        let initials = names[0].charAt(0)
        if (names.length > 1) {
          initials += names[names.length - 1].charAt(0)
        }

        return initials
      }
    }
  }
})
</script>

VueJS 3 documentation https://v3.vuejs.org/guide/typescript-support.html#using-with-composition-api states:

On setup() function, you don't need to pass a typing to props parameter as it will infer types from props component option.

However, i keep getting a complication error, and i am not sure what i am missing

Result:

Failed to compile.

TS2339: Property 'user' does not exist on type 'Readonly<LooseRequired<Readonly<{ [x: number]: string; } & { length?: number | undefined; toString?: string | undefined; toLocaleString?: string | undefined; concat?: string[] | undefined; join?: string | undefined; ... 15 more ...; includes?: ((searchElement: string, fromIndex?: number | undefined) => boolean) | un...'.
    38 |   },
    39 |   setup (props) {
  > 40 |     const initial = props.user.agent_id.charAt(0)
       |                           ^^^^
    41 |     const color = computed(() => {
    42 |       return colors[initial] || 'green'
    43 |     })

Notes: adding a @ts-ignore above the line in question does remove the error, but that's not solving the issue.

I've tried deleting node_modules and restarting everything to ensure its not a glitch

its running in a docker image

like image 262
TotalWipeOut Avatar asked Jun 21 '21 20:06

TotalWipeOut


People also ask

How do you define props in Vue composition API?

You define a prop named disabled in MyComponent. vue . ... and then add the component like this, passing in disabled . Note that :disabled="true" and just disabled mean the same thing in both cases - when props are defined or not.

Can I use Vue 3 without composition API?

Vue 3 does not require using the Composition API.

How do you pass an object as a prop in Vue?

To pass in the properties of an object as props, we can use the v-bind without the argument. Then the properties of post will be passed into blog-post as prop values. The property names are the prop names.

Does Vue 3 support options API?

It is not compulsory that you have to use Composition API in order to use Vue 3. It still supports Options API at its core. It is also possible to use both methods together in a single Vue component.


1 Answers

For validator and default in the prop declarations, the Vue docs prescribe either (1) using an arrow function, or (2) provide an explicit this parameter:

WARNING

Because of a design limitation in TypeScript when it comes to type inference of function expressions, you have to be careful with validator and default values for objects and arrays:

import { defineComponent, PropType } from 'vue'

interface Book {
  title: string
  year?: number
}

const Component = defineComponent({
  props: {
    bookA: {
      type: Object as PropType<Book>,
      // Make sure to use arrow functions
      default: () => ({
        title: 'Arrow Function Expression'
      }),
      validator: (book: Book) => !!book.title
    },
    bookB: {
      type: Object as PropType<Book>,
      // Or provide an explicit this parameter
      default(this: void) {
        return {
          title: 'Function Expression'
        }
      },
      validator(this: void, book: Book) {
        return !!book.title
      }
    }
  }
})

Anders Hejlsberg, lead architect of TypeScript, explains the issue in a GitHub comment:

This is a design limitation. Similar to #38872. A[n] arrow function with no parameters is not context sensitive, but a function expression with no parameters is context sensitive because of the implicit this parameter. Anything that is context sensitive is excluded from the first phase of type inference, which is the phase that determines the types we'll use for contextually typed parameters. So, in the original example, when the value for the a property is an arrow function, we succeed in making an inference for A before we assign a contextual type to the a parameter of b. But when the value is a function expression, we make no inferences and the a parameter is given type unknown.


Original answer:

One of your props doesn't match the expected signature of PropOptions, which apparently breaks the type inference for the props argument in setup(). Specifically, TypeScript doesn't see that the signature of size.validator matches up with the type of PropOptions.validator for some reason.

Interestingly, if you change validator to an arrow function, the type inference for props succeeds:

export default defineComponent({
  props: {
    size: {
      type: String,
      required: false,
      default: 'lg',
      //validator: function (value: string) { /*...*/ },
      validator: (value: string) => { /*...*/ },
    },
  }
})
like image 124
tony19 Avatar answered Oct 18 '22 09:10

tony19