Async Loading of Vue Component, $refs undefined in Mounted lifecycle - vue.js

I have a simple button component that is asynchronously loaded into another component.
I have a call in mounted that references the button component but I get an error the it is undefined. If I import the button as you normally would there are no issues. I am attempting to load as asynchronously as I have other points where I would call the button however in this case it should be ready to go on mounted.
It was my understanding that vue will load the component when you need it however I need it on mounted but can not access it. Am OI going about this wrong? Maybe I don't completely understand it all yet.
**error**
Error in v-on handler: "TypeError: Cannot read property '$el' of undefined"
**code**
<template>
<div class"wrapper">
<button ref="flipBtn" /> I am the text </button>
</div>
</template>
// Works if I import this way
// import button from '#/components/inline-components/button'
export default {
components: {
// button
button: () => { import ('#/components/inline-components/button') }
},
mounted() {
// I have tried wrapping in a this.$nextTick(() => {}) but no luck
this.$refs.flipBtn.$el.focus({ preventScroll: true })
}
}

Thanks to #HusamIbrahim suggestions I was able to get rid of the error by using both a custom directive and a conditional check of the $refs in a later function call
**error**
Error in v-on handler: "TypeError: Cannot read property '$el' of undefined"
**code**
<template>
<div class"wrapper">
<button ref="flipBtn" v-focus /> I am the text </button>
</div>
</template>
// Works if I import this way
// import button from '#/components/inline-components/button'
export default {
components: {
// button
button: () => { import ('#/components/inline-components/button') }
},
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}
methods {
// Wasn't apart of my original question but I was able to remove the error on it using comments above.
glideAfter () {
// Give Flip button focus, Hidden button that helps with keyboard focus
if (this.$refs && this.$refs.flipBtn) {
this.$refs.flipBtn.$el.focus({ preventScroll: true })
}
}
}

Related

Modify child component prop/data in parent's event handler

I am using vue.js 2 and ant design. I encountered such an issue when trying to change the loading state of a button in a modal, which is in an embedded component. Here is a similar example:
Code of modal component:
Modal.vue
<template>
<a-button :loading="loading" #click="onSubmit">Submit</a-button>
</template>
<script>
export default {
data(){
return {
loading: false
}
},
methods: {
onSubmit(){
this.$emit('submit')
}
}
}
</script>
Index.vue
<modal
#submit="submit" />
<script>
export default {
methods: {
submit(){
await axios.create(blabla...) //some async api call
}
}
}
</script>
Two failed approach:
Set the value of loading before and after $emit in onSubmit. It won't wait for the async request to finish.
Make loading a prop of Modal component and change it in the event handler. Nothing happens.
The only workable I have found so far is to create a method like setLoading in child component, pass that to the event handler as a parameter and call it. I was wondering if there is a more succinct way and what is the mechanism behind these.
you should pass the loading state as props. and add async before submitting the method. don't know why you mention failed cases. hard to tell without looking in that code. but try again with this code. maybe there are errors. so wrap in a try-catch block when you fetch details.
<modal
#submit="submit" :loading="loading" />
<script>
export default {
data(){
return {
loading: false,
}
}
methods: {
async submit(){ // add async keyword
this.loading = true
try{ // use try-catch block. see errors if there are any.
await axios.create(blabla...) //some async api call
}
catch(error){
console.log(error.response)
}
this.loading = false
}
}
}
</script>
Modal.vue
<template>
<a-button :loading="loading" #click="onSubmit">Submit</a-button>
</template>
<script>
props: {
loading: Boolean
}
</script>

Buefy switch how to click on it in parent component

I have a Vue 2 app with Buefy. there is a component which contains Buefy switch. In some moment I need to click on the switch component. So I add ref property to the switch component and then I call it like this.$refs.switcherName.click(). But it throws me an error click is not a function. If I dump the $refs.switcherComponent it is component instance.
This is the code where I call the click()
watch: {
storeSelected(prevVal, newVal) {
this.$refs.onlySelectedToggler.click();
},
},
Can you be more specific on what you're trying to accomplish please? The error does tell you that "click()" is not a function of a Buefy switch, which is true. To change whether the switch is "on" or "off", you can use a v-model and data attribute to just change the value. For example:
<template>
<section>
<b-field>
<b-switch v-model="isSwitched">Default</b-switch>
</b-field>
<b-button #click="changeSwitch">Click to Change</b-button>
</section>
</template>
<script>
export default {
data() {
return {
isSwitched: false
}
},
methods: {
changeSwitch () {
this.isSwitched = !this.isSwitched
}
}
}
</script>

How to dynamically mount vue component with props

Scenario / context
I have an overview component which contains a table and an add button. The add button opens a modal component. When i fill in some text fields in the modal and click the save button, a callback (given as prop) is called so the parent component (the overview) is updated. The save button also triggers the model toggle function so the model closes.
So far works everything like expected but when i want to add a second entry, the modal is "pre-filled" with the data of the recently added item.
Its clear to me that this happens because the model component keeps mounted in the background (so its just hidden). I could solve this by "reset" the modals data when the toggle function is triggered but i think there should be a better way.
I have a similar issue when i want to fetch data in a modal. Currently i call the fetch function in the mounted hook of the modal. So in this case the fetch happens when the parent component mounts the modal. This does not make sense as it should only (and each time) fetch when the modal is opened.
I think the nicest way to solve this is to mount the modal component dynamically when i click the "add" (open modal) button but i can't find how i can achieve this. This also avoids that a lot of components are mounted in the background which are possibly not used.
Screenshot
Example code
Overview:
<template>
<div>
// mount of my modal component
<example-modal
:toggleConstant = modalToggleUuid
:submitHandler = submitHandler />
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
someList: [],
}
},
mounted() {
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Modal:
<template>
<div>
<input v-model="item.type">
<input v-model="item.name">
<input v-model="item.location">
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
item: {},
}
},
mounted() {
// in some cases i fetch something here. The data should be fetched each time the modal is opened
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Question
What is the best practive to deal with the above described scenario?
Should i mount the modal component dynamically?
Do i mount the component correctly and should i reset the content all the time?
You are on the right way and in order to achieve what you want, you can approach this issue with v-if solution like this - then mounted() hook will run every time when you toggle modal and it also will not be present in DOM when you are not using it.
<template>
<div>
// mount of my modal component
<example-modal
v-if="isShowModal"
:toggleConstant="modalToggleUuid"
:submitHandler="submitHandler"
/>
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
isShowModal: false,
modalToggleUuid: someUuid,
someList: []
};
},
mounted() {},
methods: {
showModal: function() {
this.isShowModal = true;
},
submitHandler: function(item) {
this.someList.push(item);
this.isShowModal = false;
}
}
};
</script>

The object sent as prop is undefined while testing a Vue SFC with Jest

I want to test a Vue single file component which receives a prop as input. When I mock the prop, which is an object, I get an error that the object is undefined, The error comes from the HTML where the values of the object are used. If I make the prop to be a string for example (and I remove answer.value and :class="{'active': answer.selected}" from HTML), everything works fine.
Component:
<template>
<div class="answer-container" #click="setActiveAnswer()" :class="{'active': answer.selected}">
<div class="answer">
<p>{{answer.value}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'Answer',
props: {
answer: Object,
},
methods: {
setActiveAnswer() {
this.$emit('selectedAnswer', this.answer);
}
}
}
</script>
Test file:
import { mount } from '#vue/test-utils'
import Answer from './../../src/components/Answer'
describe('Answer', () => {
it('should receive "answer" as prop', () => {
const answer = {
value: 'testAnswer',
selected: true
};
const wrapper = mount(Answer, {
propsData: {
answer: answer
}
});
expect(wrapper.props().answer.value).toBe('testAnswer');
})
})
The error I get is:
TypeError: Cannot read property 'selected' of undefined
Please advise what am I doing wrong. Thanks!
I managed to fix this by adding a v-if="answer" on <div class="answer-container" ..., which is quite strange (as this is not async data) since the code works fine when checking the application in the browser - the problem only appeared while unit testing the component. I suppose there is also a fix in a Jest/Unit testing way, something like declaring the prop after the component finished rendering/mounting...

mixin method is undefined in created hook of component

<template>
<section>
Top
</section>
</template>
<script>
import api from '../../server/api.ts';
export default {
name: 'Questions',
mixins: [api],
data() {
return {
templates : getTemplates(),
};
},
created() {
// **[Vue warn]: Error in created hook: "ReferenceError: getTemplates is not defined"**
this.templates = getTemplates();
},
};
</script>
getTemplates function is working fine if i click on link but getting error in all life hooks of Vue js component.
Thanks for help in advance !
You forgot this for the function. Loading mixins has the same effect as if the content in it would be in your actual component. You need to call the methods the same way as you would call a local function. Always with this.
So change it to:
created() {
this.templates = this.getTemplates();
},