2
votes

When passing a prop to a child component in Vue, the documentation says:

In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console.

The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case, it’s best to define a local data property that uses the prop as its initial value:

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

We are using typescript. The syntax for "defining a local data property" is as follows (to my understanding):

<script lang="ts">
import Vue from 'vue'
import { Component } from 'vue-property-decorator'

@Component
export default class App extends Vue {
  // Data property
  myDataProperty: string;
</script>

And the syntax for a prop is:

@Component
export default class App extends Vue {
  // Makes a "exampleProperty" a component prop with the default value of 'Example'
  @Prop({default: 'Example'})
  exampleProperty: string
}

So, we tried to follow the documentation, and ended up with:

parentComponent.vue

<template>
  <childComponent testProperty='test' />
</template>

childComponent.vue

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class childComponent extends Vue {
  @Prop(
    {
      default: 'notTest',
      validator: (component) => {
        return [
          'notTest',
          'test',
        ].indexOf(component) > -1;
      },
    },
  )
  testProperty!: string;
  testProperty = this.testProperty;
</script>

That, predictably, errored with `Duplicate identifier testProperty.

So, we tried

...
      testProperty!: this.testProperty;
...

which resulted in

Duplicate identifier 'testProperty'. Property 'testProperty' has no initializer and is not definitely assigned in the constructor. Subsequent property declarations must have the same type. Property 'testProperty' must be of type 'this', but here has type 'any'.

So, I decided to try the "vue-class-component" decorator.

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component({
  data: function(){
    return {
      testProperty: this.testProperty,
    }
  }
})
export default class childComponent extends Vue {
  @Prop(
    {
      default: 'notTest',
      validator: (component) => {
        return [
          'notTest',
          'test',
        ].indexOf(component) > -1;
      },
    },
  )
  testProperty!: string;
  testProperty = this.testProperty;
</script>

This resulted in the error Property 'testProperty' does not exist on type 'Vue'.

I would like to, in a handler, do this.testProperty = 'newProperty' at some point, but cannot, because that would be directly modifying a prop.

How can I define a local data property that uses a prop as its initial value in Typescript?

EDIT:

If I do none of the above, and simply define the prop, with no attempt to define a local data property that uses the prop as its initial value, and then do

this.testProperty = 'test'

in a handler, this error is displayed in the chrome console:

vue.runtime.esm.js[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "testProperty"

1
You do not need to use testProperty = this.testProperty;, and neither do you need to declare testProperty in your component data.Terry
Can you clarify? I would like to have the validator for the prop at the the childComponent levelCaleb Jay
Just use @Prop(..) testProperty!: string should be sufficient. Also, by declaring it in the data will cause overwriting to happen (because data and property share the same namespace), which will cause Vue to throw this warning: [Vue warn]: The data property "testProperty" is already declared as a prop. Use prop default value instead. The @Prop decorator will handle (1) assigning the default value, (2) performing the validation: github.com/kaorun343/…Terry
Hmm. Can you check out the edit I made? If I do only the @Prop... bit, I get an error when I later try to modify that property.Caleb Jay
You can't mutate a prop: whether you do it with TS or JS you will get the same warning. If you want, copy that prop over into the data object but use a different key, eg. return { testPropertyLocal: this.testProperty }. Then you can reassign this.testPropertyLocal with any value you want at a later point. However, this feels like an XY problem to me: you might want to look into why you need to mutate this prop, and perhaps look at using computed properties instead.Terry

1 Answers

2
votes

I will summarise my comments into a single coherent answer: the problem you are seeing is that you have already defined this.testProperty by declaring it as a prop: doing testProperty = this.testProperty is a circular reference at best. Using the @Prop decorator alone will do the mapping of the attribute in the template to the variable.

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class childComponent extends Vue {
  @Prop(
    {
      default: 'notTest',
      validator: (component) => {
        return [
          'notTest',
          'test',
        ].indexOf(component) > -1;
      },
    },
  )
  testProperty!: string;

  // Map prop to local data property
  testPropertyLocal = this.testProperty;
</script>

Also, remember this caveat: VueJS properties must be kebab-case in templates and camelCase in JS. So, you need to update your child component reference to:

<template>
  <childComponent test-property='test' />
</template>