Why does a keydown event get broadcasted to a newly rendered component? - vue.js

I want to conditionally render a component based on the user's keypress.
Once the new component is rendered, an input field should get focused.
For some reason, Vue broadcasts the keypress I use to render the component to the new component! The result is that the key I pressed to render the component gets displayed in the input field!
How is that even possible? The keypress triggers the mounting of the new component and the focusing of the input only happens after it is mounted.
Minimum working example:
// App.vue
<template>
<div>
<p>App</p>
<Hello v-if="view == 'hello'" />
</div>
</template>
<script>
import Hello from "./components/Hello.vue";
export default {
components: {
Hello
},
data() {
return {
view: null
};
},
methods: {
changeView() {
this.view = "hello";
}
},
created() {
document.addEventListener("keydown", this.changeView);
}
};
</script>
// Hello.vue
<template>
<div>
<p>Hello</p>
<input ref="input" type="text" />
</div>
</template>
<script>
export default {
mounted() {
this.$refs.input.focus();
}
};
</script>

This is because the events are flowing like this:
keydown renders the new component -> focus moved to new component -> keyup fires in new component (where is focus now) and character stays there.
Change the trigger to keyup and it should work.

Related

how do I pass the value from child to parent with this.$emit

What I trying to achieve here is to pass the const randomNumber inside the child component [src/components/VueForm/FormQuestion.vue] that need to be passed to parent component [src/App.vue]. Therefore I use $emit to pass the date, but since this is my first time working with $emit, I am not really sure how to do that. Could someone help me with this.
In order to run this app, I would add a working code snippet. Click on the start button and fill in the input fields. When the input field validates correctly it will pop up the button and if the user clicks on that is should pass the data to the parent. At the end it should be stored inside the App.vue in localStorage, so therefore I want to receive the randomNumber from that child component.
working code snippet here
// child component
<template>
<div class="vue-form__question">
<span class="question" :class="{ big: !shouldShowNumber }"> {{ getRandomNumber() }} </span>
</div>
</template>
<script>
export default {
methods: {
getRandomNumber() {
const randomNumber = Math.floor((Math.random() * 3) + 1);
const question = this.question.question;
this.$emit('get-random-number', question[randomNumber]);
return question[randomNumber];
}
}
};
// parent component
<template>
<div id="app">
<vue-form
:data="formData"
#complete="complete"
#getRandomNumber="newRandomNumber"
></vue-form>
</div>
</template>
<script>
import VueForm from "#/components/VueForm";
import data from "#/data/demo";
export default {
data() {
return {
formData: data
}
},
components: {
VueForm
},
created() {
this.complete()
},
methods: {
complete(data) {
// Send to database here
// localStorage.setItem('questions', data.map(d => d.question[this.randomNumber] + ': ' + d.answer));
},
}
};
</script>
v-on:get-random-number (or the superior short-hand syntax: #get-random-number). Just like you'd listen to any other event, such as #click or #mouseenter.
Though I don't know off the top of my head if dashes are valid in event names. Might have to camelcase it.

Vue `vm.$on()` callback not working in parent when event is emitted from dynamic component child

I'm experiencing a problem where a custom event (swap-components) emitted from a dynamic component (A.vue or B.vue) is not being listened to correctly in the parent of the dynamic component (HelloWorld.vue).
Here is the source on GitHub (created using vue cli 3).
Here is a live demo showing the problem.
In the live demo, you'll see that clicking the button in the dynamic component with background color DOES NOT change the dynamic component. But when clicking the button below the background color (which originates in the HelloWorld.vue parent), the dynamic component DOES INDEED change.
Why is this happening and how to fix it?
Below I'll copy over the contents of the 3 main files of interest into this post:
HelloWorld.vue (the parent)
A.vue (sub component used in dynamic component)
B.vue (sub component used in dynamic component)
HelloWorld.vue:
<template>
<div>
<h1>The dynamic components ⤵️</h1>
<component
:is="current"
v-bind="dynamicProps"
></component>
<button
#click="click"
>Click me to swap components from within the parent of the dynamic component</button>
</div>
</template>
<script>
import A from "./A.vue";
import B from "./B.vue";
export default {
data() {
return {
current: "A"
};
},
computed: {
dynamicProps() {
return this.current === "A" ? { data: 11 } : { color: "black" };
}
},
methods: {
click() {
this.$emit("swap-components");
},
swapComponents() {
this.current = this.current === "A" ? "B" : "A";
}
},
mounted() {
this.$nextTick(() => {
// Code that will run only after the
// entire view has been rendered
this.$on("swap-components", this.swapComponents);
});
},
components: {
A,
B
},
props: {
msg: String
}
};
</script>
A.vue:
<template>
<section id="A">
<h1>Component A</h1>
<p>Data prop sent from parent: "{{ data }}"</p>
<button #click="click">Click me to swap components from within the dynamic component</button>
</section>
</template>
<script>
export default {
props: ["data"],
methods: {
click() {
this.$emit("swap-components");
}
}
};
</script>
B.vue:
<template>
<section id="B">
<h1>Component B</h1>
<p>Color prop sent from parent: "{{ color }}"</p>
<button #click="click">Click me to swap components from within the dynamic component</button>
</section>
</template>
<script>
export default {
props: ["color"],
methods: {
click() {
this.$emit("swap-components");
}
}
};
</script>
I'm guessing this is because the event listener is listening for a swap-components event emitted by the parent component itself. Perhaps you can fix that by listening for a swap-components event from the child component then emitting an event on the parent component.
<template>
<div>
<h1>The dynamic components ⤵️</h1>
<component
:is="current"
v-bind="dynamicProps"
#swap-components="$emit('swap-components')"
></component>
<button
#click="click"
>Click me to swap components from within the parent of the dynamic component</button>
</div>
</template>
Or you can call your method directly when the event is emitted by the child component ..
<template>
<div>
<h1>The dynamic components ⤵️</h1>
<component
:is="current"
v-bind="dynamicProps"
#swap-components="swapComponents"
></component>
<button
#click="click"
>Click me to swap components from within the parent of the dynamic component</button>
</div>
</template>
this is not bound to the context anymore when you use function. It is only limited to the function scope. Use arrow function to let this bound to the parent context.
Change:
this.$nextTick(function() {
With:
this.$nextTick(() => {

Vue js how to use props values to v-model

I have two component namely App.vue and hello.vue
In App component I import the hello component and use props to pass relevant data to the hello component.
there I bind data which are took from the App component.
In my hello component I have a input box bind to the passed value.
My final goal is pass values as props to the hello component and change it and finally
pass that edited values to the backend using the save method.
How do I achive this?
This is what I have done up to now.
App.vue
<template>
<div id="app">
<hello-world :msg="'hello good morning'"></hello-world>
</div>
</template>
<script>
import helloWorld from "./components/HelloWorld";
export default {
components: {
helloWorld
}
};
</script>
hello.vue
<template>
<div>
<input type="text" :value="msg">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
In my hello component's input field v-model is not possible. I want something similar to the v-model.
You cannot use prop to bind to v-model. Child component is not supposed to modify prop passed by the parent component.
You will have to create a copy of prop in your child component if you wish to use prop with v-model and then watch prop like this:
<template>
<div>
<input type="text" #input="onInput" v-model="msgCopy">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
data() {
return { msgCopy: '' };
},
methods: {
onInput(newInputValue) {
this.$emit('msgChange', newInputValue);
}
}
watch: {
msg(newVal) {
this.msgCopy = newVal;
}
}
};
</script>
Also, notice the use of event handler #input to pass changed prop back to the parent component via event. As a syntax sugar, you can make your Hello component work as a custom form input control by adopting to v-model lifecycle.

vue reload child component

I'm using vue, version 2.5.8
I want to reload child component's, or reload parent and then force children components to reload.
I was trying to use this.$forceUpdate() but this is not working.
Do You have any idea how to do this?
Use a :key for the component and reset the key.
See https://michaelnthiessen.com/force-re-render/
Add key to child component, then update the key in parent. Child component will be re-created.
<childComponent :key="childKey"/>
If the children are dynamically created by a v-for or something, you could clear the array and re-assign it, and the children would all be re-created.
To simply have existing components respond to a signal, you want to pass an event bus as a prop, then emit an event to which they will respond. The normal direction of events is up, but it is sometimes appropriate to have them go down.
new Vue({
el: '#app',
data: {
bus: new Vue()
},
components: {
child: {
template: '#child-template',
props: ['bus'],
data() {
return {
value: 0
};
},
methods: {
increment() {
this.value += 1;
},
reset() {
this.value = 0;
}
},
created() {
this.bus.$on('reset', this.reset);
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<button #click="() => bus.$emit('reset')">Reset</button>
</div>
<template id="child-template">
<div>
{{value}} <button #click="increment">More!</button>
</div>
</template>
I'm using directive v-if which is responsible for conditional rendering. It only affects reloading HTML <template> part. Sections created(), computed are not reloaded. As I understand after framework load components reloading it is not possible. We can only re render a <template>.
Rerender example.
I have a Child.vue component code:
<template>
<div v-if="show">
Child content to render
{{ callDuringReRender() }}
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
,
methods: {
showComponent() {
this.show = true
},
hideComponent() {
this.show = false
},
callDuringReRender() {
console.log("function recall during rendering")
}
}
}
</script>
In my Parent.vue component I can call child methods and using it's v-if to force the child rerender
<template>
<div>
<input type="button"
value="ReRender the child"
#click="reRenderChildComponent"/>
<child ref="childComponent"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
methods: {
reRenderChildComponent(){
this.$refs.childComponent.hideComponent();
this.$refs.childComponent.showComponent()
}
},
components: {
Child
}
}
</script>
After clicking a button in console You will notice message "function recall during rendering" informing You that component was rerendered.
This example is from the link that #sureshvv shared
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// ...
this.$forceUpdate(); // Notice we have to use a $ here
// ...
}
}
}
I've found that when you want the child component to refresh you either need the passed property to be output in the template somewhere or be accessed by a computed property.
<!-- ParentComponent -->
<template>
<child-component :user="userFromParent"></child-component>
</template>
<!-- ChildComponent -->
<template>
<!-- 'updated' fires if property 'user' is used in the child template -->
<p>user: {{ user.name }}
</template>
<script>
export default {
props: {'user'},
data() { return {}; }
computed: {
// Or use a computed property if only accessing it in the script
getUser() {
return this.user;
}
}
}
</script>

Component Declaration And Communication

I have added a component declaration to the default main.js file which is generated during the Webpack project creation process as
import Modal from '#/components/Modal'
Vue.component('modal', Modal)
And in the App.vue, I have
<modal v-show="showModal"></modal>
<button id="show-modal" v-on:click="showModal = true">Click to have a modal</button>
And they work fine. Now, I need to setup a "props down, events up" communication channel between the parent and a child. To do so, I need to add a property, called 'isActive', the Modal component so that the root component can send a message to the child component, that is
<modal isActive="showModal"></modal>
<button id="show-modal" v-on:click="showModal = true">Click to have a modal</button>
I guess the component declaration should be something like:
Vue.component('modal', {
props: ['isActive'],
Modal
})
It doesn't work, however, due to
Failed to mount component: template or render function not defined.
I have tried different variants without a luck.
My second question is that how a child event changes its parent data. For example, in the child component
<button class="modal-close is-large" v-on:click="closeModal"></button>
the closeModal event is handled in the following javacript code in the child component.
export default {
method: {
closeModal: function(event) {
...
}
}
}
How can I set its parent data showModal to false?
Update:
The code segment of Modal:
<template>
<div class="signin">
<div class="modal" v-bind:class="{ 'is-active': isActive }">
...
</div>
<button class="modal-close is-large" v-on:click="isActive = false"></button>
</div>
</div>
</template>
<script>
import axios from 'axios'
import _ from 'lodash'
import Notification from '#/components/Notification'
import { required, email } from 'vuelidate/lib/validators'
export default {
name: 'signin',
components: {
Notification
},
data: () => ({
isActive: true,
email: '',
...
}),
...
}
</script>
Bulma is used for styling. And the isActive is defined in the Modal. I think it needs to be changed to achieve "props down".
As it looks, your file /components/Modal contains a full definition of a component: the template, and the script parts for it. So you can just bind the component to the tag-name you want to use in your markup:
import Modal from '#/components/Modal'
Vue.component('modal', Modal)
This is basically what you had in the beginning. To pass properties to this component, add the props-line directly to your component, that is into /components/Modal:
...
export default {
name: 'signin',
components: {
Notification
},
props: ['isActive'],
data: () => ({
...
As for the second question, how to communicate back to the parent, have a look at Vue's Custom Events. Basically, your Modal component could issue a "close"-event like this:
methods: {
closeModal: function(event) {
this.$emit('modalClose')
}
}
and when you use the component, you could listen to it like this:
<modal v-bind:isActive="showModal" v-on:modalClose="showModal = false"></modal>
Note that you should use v-bind for providing the value to isActive. If you don't use v-bind, the value is just passed once when the component is created. This means, the component would never see a change to this prop when it is changed by the parent. By using v-bind, changes by the parent to this attribute are pushed down to the child-component, so the Modal actually sees the updated value and can react to it.