"Expected Object, got Promise." error when promise passed as a prop - javascript

I just try to pass a Promise as a Prop to a child component which expect a Promise but I have an error that tells the expected type is Object.
I use TypeScript, vue-property-decorator and vue-class-component for my project.
My code looks like this:
<template>
<child-component :listPromise="listAsPromise"/>
</template>
<script lang="ts">
import { Component, Vue, Prop, Inject } from 'vue-property-decorator'
import { EditableReferentielModel } from '#/api/model/referentiel'
import ReferentielResource from '#/api/resources/referentiel'
import { AxiosResponseExt } from '#/api/resources/index'
export default class ParentComponent extends Vue {
#Inject()
referentielResource: ReferentielResource
listAsPromise: Promise<EditableReferentielModel[] & AxiosResponseExt> = this.referentielResource.getStadeDeveloppement()
}
</script>
<script lang="ts">
import { Component, Vue, Prop, Inject } from 'vue-property-decorator'
import ReferentielResource from '#/api/resources/referentiel'
import { EditableReferentielModel } from '#/api/model/referentiel'
import { AxiosResponseExt } from '#/api/resources/index'
#Component
export default class ChildComponent extends Vue {
#Inject()
referentielResource: ReferentielResource
#Prop({ default: null })
value: EditableReferentielModel | null
#Prop({ default: null })
listPromise: Promise<EditableReferentielModel[] & AxiosResponseExt>
referentiel: EditableReferentielModel[] = []
selected: EditableReferentielModel | null = this.value
async mounted () {
this.referentiel = await this.listPromise
}
}
</script>
With this code, I still have this error: [Vue warn]: Invalid prop: type check failed for prop "listPromise". Expected Object, got Promise. even if the listPromise type is set to any.
What is wrong ?

Related

Trouble setting up basic Pinia store with Vue options API

I am new to Pinia, and am having trouble setting up just a basic store. I have been following Pinia's own documentation, and cannot seem to read any state whatsoever from the vue component I'm mapping to the Pinia store.
In my app I have:
import { createPinia } from 'pinia';
export default function initApp(el) {
let app = createApp(MenuApp);
app.use(router).use(createPinia()).mount(el);
}
I set up a super basic Pinia store, just to get started:
import {defineStore} from 'pinia';
export const useTestPiniaStore = defineStore('testStore', {
state: () => {
return {
name: 'bob'
}
},
})
In my vue component I have:
<template>
<div class="menu-page">
<h1>{{name}}</h1>
</div>
</template>
<script>
import { mapState } from 'pinia';
import useTestPiniaStore from '#store/modules/piniaStore';
export default {
computed: {
...mapState(useTestPiniaStore['name']),
}
}
</script>
Pinia appears in my Vue dev tools, but no stores appear within it, and I get the error
Cannot read properties of undefined (reading 'name')
I don't understand what I am doing wrong here. If anyone can give some pointers that would be so appreciated.
mapState() requires two arguments, but you've passed it only one.
The 1st argument should be useTestPiniaStore, and the 2nd should be an array of state properties to map (or an object). It looks like you're trying to reference name from useTestPiniaStore, which would be undefined.
Your computed prop should look like this:
<script>
import { mapState } from 'pinia'
import { useTestPiniaStore } from '#/store'
export default {
computed: {
...mapState(useTestPiniaStore, ['name']), 👈
},
}
</script>
demo

Testing a component with Jest Error in render: "TypeError: Cannot read property 'template' of null"

I have this component in my codebase,
<template>
<v-dialog v-model="modalVisible" content-class="muc-modal" max-width="350" persistent>
<v-card>
<component :is="component"/>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Watch } from "vue-property-decorator";
import { namespace } from "vuex-class";
const Modal = namespace("Modals");
#Component
export default class AppModal extends Vue {
public component: any = null;
#Modal.State
public modalVisible!: boolean;
#Modal.State
public modalComponent!: string;
#Modal.State
public modalComponentPath!: string|null;
get injectedComponent() {
return this.modalComponent;
}
#Modal.Mutation
public hideModal!: () => void
#Watch('injectedComponent')
onModalComponent(componentName: string) {
if(!componentName) return;
this.component = Vue.component(componentName, () => import(`./${this.modalComponentPath}`));
}
}
</script>
<style scoped lang="scss">
.muc-modal {
width:350px;
max-width:90%;
}
</style>
It is modal component that takes another component, this is run via mutation on the modal store,
import { VuexModule, Module, Mutation, Action } from "vuex-module-decorators";
#Module({ namespaced: true })
export default class Modals extends VuexModule {
//#region "State"
public modalVisible: boolean = false;
public modalComponent: string|null = null;
public modalComponentPath: string|null = null;
//#endregion "State"
//#region "Mutations"
#Mutation
public showModal(modalComponent:string, modalComponentPath: string|null = null) {
this.modalVisible = true;
this.modalComponent = modalComponent
this.modalComponentPath = modalComponentPath ? modalComponentPath : modalComponent
}
#Mutation
public hideModal() {
this.modalVisible = false;
this.modalComponent = null;
this.modalComponentPath = null;
}
//#endregion "Mutations"
//#region "Getters"
get getVisibility(): boolean {
return this.modalVisible
}
//#endregion "Getters"
//#region "Actions"
//#endregion "Actions"
}
I am wanting to write some tests that a) test the modal display correctly when showModal() mutation is run, b) that it gets hidden correctly when hideModal() mutation is run.
This is my current test file,
import { shallowMount, createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import Modals from '#/store/modal';
import AppModal from '#/components/AppModal.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('AppModal.vue', () => {
let store: any = null;
//let mutations = modals.mutations
beforeEach(() => {
store = new Vuex.Store({
modules: {
"modals" : Modals
}
})
});
it('shows modal when modalVisible is set to true', () => {
console.log(store);
const wrapper = shallowMount(AppModal, {store, localVue});
// const modal = wrapper.find('.muc-modal')
// console.log(modal);
})
});
running this test I get the following response,
console.error node_modules/vuex/dist/vuex.common.js:916
[vuex] module namespace not found in mapState(): Modals/
console.error node_modules/vuex/dist/vuex.common.js:916
[vuex] module namespace not found in mapState(): Modals/
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in render: "TypeError: Cannot read property 'template' of null"
found in
---> <AppModal>
and i have no clue why, can anyone help shed some light on this for me?
This error -
TypeError: Cannot read property 'template' of null
Is a direct result of this coding error -
<component :is="null" />
Why?
The component tag should never have a null value for the is attribute, even if it's momentary. null isn't a valid component, and Jest correctly finds this error even when you don't see it in the browser.
In your above code, the initial value for the property component (which is probably a bad variable name) is null, so that's where this is coming from. A simple fix would be to set a valid component as an initial value so that it is never null.

Why does my vue code not work as expected?

there are two components in div,When the two components were rendered together, I clicked the button to switch properly, but in the case of rendering only one component, the switch becomes abnormal.
this is my code
Base.vue
<template>
<div :id="id">{{msg}}</div>
</template>
<script lang='ts'>
import { Component, Prop } from "vue-property-decorator";
import Vue from "vue";
#Component
export default class Base extends Vue {
id!: string;
msg = "this is Base";
}
</script>
child.vue(no template)
<script lang='ts'>
import Base from "#/components/Base.vue";
import { Prop, Component } from "vue-property-decorator";
#Component
export default class extends Base {
#Prop({ default: "helloWorld" })
childId!: string;
constructor() {
super();
this.id = this.childId;
this.msg = "this is Child " + this.childId;
}
}
</script>
App.vue(display these components)
<template>
<div id="app">
<Child v-show="!show" childId="child1" style="color:#f00;"/>
<button #click="click">change</button>
<Child v-show="show" childId="child2" style="color:#f0f;"/>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Child from "#/components/Child.vue";
import Component from "vue-class-component";
#Component({
components:{
Child,
}
})
export default class App extends Vue {
show= false;
click() {
this.show = !this.show;
}
}
</script>
and click the button the result is
These results are expected. But if all the v-show in the app. vue above are changed to v-if, the result is confusing
then click the button the result is
In our expectation it should display child2 here. So why does this happen?
Your first click creates the the show-property which didn't exist because you didn't create your data() properly.
I'll not speculate exactly in the reasons why, but I assume there might be some funny boolean casts, and the property might not be reactive since it's not in data. Either way, just create it and it'll work as you intended:
export default class App extends Vue {
data(){
return {
show: false
}
},
click() {
this.show = !this.show;
}
}
Thanks!!
I solved this problem when I added different keys to the two Child components
<Child v-if="!show" childId="child1" key="hello1" style="color:#f00;" />
<Child v-if="show" childId="child2" key="hello2" style="color:#f0f;" />
I think the reason is Vue's diff algorithm, Vue considers these two components to be the same component
Because when you use v-if, it will use the single same Child component. The this.msg will only change once in the constructor. The msg will not change when the childId props changed, so you need the Watch. When the childId changed, then to update the msg
Child.vue
<script lang='ts'>
import Base from "#/components/Base.vue";
import { Prop, Component, Watch } from "vue-property-decorator";
#Component
export default class extends Base {
#Prop({ default: "helloWorld" })
childId!: string;
#Watch('childId')
onChildIdChanged(val: any) {this.msg = "this is Child " + val}
constructor() {
super();
this.id = this.childId;
this.msg = "this is Child " + this.childId;
}
}
</script>

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

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"
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>

vue-property-decorator: Prop being mutated?

I use vue-property-decorator, it's a simple component and I got error message:
[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: "message"
What this message means? and how can I solve this?
here is my code for example:
<template>
<v-layout row justify-center>
<v-dialog v-model="dialog">........</v-dialog>
</v-layout>
</template>
<script lang="ts">
import { Component, Prop } from 'vue-property-decorator';
#Component({})
export default class SomeModal extends ... {
#Prop() public dialog?: boolean;
#Prop() public message?: string;
constructor() {
super();
}
public showError(er) {
this.message = er.message;
this.dialog = true;
}
}
</script>
<style scoped lang="scss">
</style>
I am not used with this syntax for vue, but the message is pretty clear : you need to define a data property, or a computed variable. That means either :
data: {
dialogData: ''
}
constructor() {
super();
this.dialogData = this.dialog;
}
or :
computed: {
dialogData() {
return this.dialog;
}
}
See the vuejs doc on computed properties.
Edit : with vue-property-decorator, it could be :
#Component
export default class YourComponent extends Vue {
// your code here...
private _dialogData: string = '';
constructor() {
super();
this._dialogData = this.dialog;
}
}

Categories