I have a view which has a recursive component A, which in turn embeds a component B. In component B changes are made, which should be passed via emit to component A and these should be passed directly to the view.
This "chain" works, but only the first time:
Komponent B:
// Emit in component B
function emitToParent() {
console.log("component B: ", props.current.id);
emits('changeParentReactiveData', props.current.id, data);
}
Komponent A:
// Receiving the emit in Komponent A and passing it to the view
<component-b #change-parent-reactive-data="emitToParent" />
function emitToParent(id, data) {
console.log("component A: ", id);
emits("changeParentReactiveData", id, data);
}
View:
// Receiving the emit in the View
<component-a #change-parent-reactive-data="setReactive" />
function setReactive(id, data) {
console.log("view: ", id);
}
Outcome console.log:
component B: 262194
component A: 262194
view: 262194
component B: 262187
component A: 262187
component B: 262193
component A: 262193
So as you can see, the last emit to the view component happens only the first time. Why? Does anyone have an idea?
Your code should work, As I am not able to setup the Vue 3 in the below code snippet. I am demoing it using Vue 2 setup. Please have a look and try to find out the root cause of the issue you are facing.
Every time you will click on Click Me text in the inner child component. It is emitting the events properly.
Vue.component('childComponent', {
template: `<div class="emit-time" #click="click()">Click me (Component A)</div>`,
methods: {
click() {
console.log('component A');
this.$emit('clickup', 'a click');
}
}
});
Vue.component('ParentComponent', {
data() {
return {
message: null
}
},
template: `<div><p v-text="message"></p><child-component #clickup="localMethod"></child-component></div>`,
methods: {
localMethod() {
console.log('component B');
this.message = 'Component B invoked and emitted the event to the parent';
this.$emit('clickup', 'a click');
}
}
});
new Vue({
el: "#app",
data: {
message: null
},
methods: {
localMethod() {
console.log('Root component');
this.message = 'Top most parent invoked'
}
}
});
#app {
padding : 20px;
}
div {
padding : 10px;
border : 1px dashed black;
}
div ~ div {
margin-top : 10px;
border-color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-text="message"></p>
<parent-component #clickup="localMethod"></parent-component>
</div>
Your code should work as it is. I have set up a demo here and it is working just fine. Maybe you can match your code with this-
View.vue
<script setup>
import Comp1 from './Comp1.vue'
const setReactive = (payload) => {
console.log('View', payload);
};
</script>
<template>
<v-app>
<v-main>
<Comp1 #change-parent-reactive-data="setReactive" />
</v-main>
</v-app>
</template>
Component 1
<script setup>
import Comp2 from './Comp2.vue'
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = (payload) => {
console.log('Component A', payload);
emits('changeParentReactiveData', payload);
};
</script>
<template>
<Comp2 #change-parent-reactive-data="emitToParent" :some-prop="262194"></Comp2>
</template>
Component 2
<script setup>
const props = defineProps(['someProp']);
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = () => {
console.log('Component B', props.someProp);
emits('changeParentReactiveData', props.someProp);
};
</script>
<template>
<v-btn #click="emitToParent()">
click
</v-btn>
</template>
Try implementing a global event bus to pass values from component to component.
Here's a blog to explain how:
https://blog.logrocket.com/using-event-bus-in-vue-js-to-pass-data-between-components/
Related
I have a useful little custom input I've been using in all of my Vue2 projects (allows you to customize with autofocus, autogrow and debounce), but now that I am working in Vue3, I've been trying to create an updated version.
I've not completed it yet, but I've come across some trouble with the Vue3's composition API :value="modelValue" syntax. In my CodeSandbox I have two inputs, one using the new syntax and the other just straight up using v-model. The later works, while the :value="valueInner" throws Extraneous non-props attributes errors.
What am I doing wrong here and how can I get this to work with that :value="modelValue" syntax?
Cheers!
Link to CodeSandbox
NOTE:
I still need to add autogrow and add all the usecases in App.vue.
CInput
<template>
<input
ref="inputRef"
data-cy="input-field"
v-if="type !== 'textarea'"
:disabled="disabled"
:type="type"
:placeholder="placeholder"
:readonly="readonly"
:required="required"
:autofocus="autofocus"
:debounce="debounce"
:value="valueInner"
/>
<!-- <input
ref="inputRef"
data-cy="input-field"
v-if="type !== 'textarea'"
:disabled="disabled"
:type="type"
:placeholder="placeholder"
:readonly="readonly"
:required="required"
:autofocus="autofocus"
:debounce="debounce"
v-model="valueInner"
/> -->
</template>
<script>
import { defineComponent, ref, onMounted, nextTick, watch } from "vue";
export default defineComponent({
props: {
/** HTML5 attribute */
disabled: { type: String },
/** HTML5 attribute (can also be 'textarea' in which case a `<textarea />` is rendered) */
type: { type: String, default: "text" },
/** HTML5 attribute */
placeholder: { type: String },
/** HTML5 attribute */
readonly: { type: Boolean },
/** HTML5 attribute */
required: { type: Boolean },
/** v-model */
modelValue: { type: [String, Number, Date], default: "" },
autofocus: { type: Boolean, default: false },
debounce: { type: Number, default: 1000 },
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const inputRef = ref(null);
const timeout = ref(null);
const valueInner = ref(props.modelValue);
if (props.autofocus === true) {
onMounted(() => {
// I don't know we need nexttick
nextTick(() => {
inputRef.value.focus();
// setTimeout(() => inputRef.value.focus(), 500);
});
});
}
watch(valueInner, (newVal, oldVal) => {
const debounceMs = props.debounce;
if (debounceMs > 0) {
clearTimeout(timeout.value);
timeout.value = setTimeout(() => emitInput(newVal), debounceMs);
console.log(newVal);
} else {
console.log(newVal);
emitInput(newVal);
}
});
function emitInput(newVal) {
let payload = newVal;
emit("update:modelValue", payload);
}
// const onInput = (event) => {
// emit("update:modelValue", event.target.value);
// };
return { inputRef, valueInner };
},
});
</script>
App.vue
<template>
<CInput :autofocus="true" v-model.trim="inputValue1" />
<CInput :autofocus="false" v-model.trim="inputValue2" />
<pre>Input Value 1: {{ inputValue1 }}</pre>
<pre>Input Value 2: {{ inputValue2 }}</pre>
</template>
<script>
import { ref } from "vue";
import CInput from "./components/CInput.vue";
export default {
name: "App",
components: { CInput },
setup() {
const inputValue1 = ref("");
const inputValue2 = ref("");
return { inputValue1, inputValue2 };
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
The full warning is:
[Vue warn]: Extraneous non-props attributes (modelModifiers) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.
CInput has more than one root node (i.e., the <input> and the comment node), so the component is rendered as a fragment. The .trim modifier on CInput is normally passed onto root node's v-model, as seen in this demo. Since the actual code is a fragment, Vue can't decide for you where to pass on the modelModifiers prop, leading to the warning you observed.
However, declaring a modelModifiers prop to receive the modifiers is enough to resolve the problem:
// CInput.vue
export default {
props: {
modelModifiers: {
default: () => ({})
}
}
}
demo
So far I have only built one App using Vue 3 + Vite.
I went with the <script setup> method only and stuck with it, this is how that looks when defining props:
<script setup>
import { onMounted } from 'vue'
const props = defineProps({
readonly: { type: Boolean },
required: { type: Boolean },
})
onMounted(() => {
console.dir(props.readonly)
})
</script>
In the Vite setup, defineProps is globally registered, seems to be the only method that does not require an import, I would not know if this is true of any other compiler methods.
v-model in Vue3 pops out as modelValue not value, maybe you can defineProp the modelValue unless its there by default always?
There is some explanation here: https://v3-migration.vuejs.org/breaking-changes/v-model.html#_2-x-syntax
Hope this applies to you and helps.
So I have Component "X" as Parent Component Or Component "Y" as
Parent Component, "a" as child component fire one or many events
Any child sibling or parent of "a" or "a" Itself can use that event
I want to use "a" as an independent component
So I Have "X" component with its own
state
mutations
actions
getters
And I have "a" component with its own
state
mutations
actions
getters
"a" a.vue file looks like this
<template>
<div>
<app-select
#change.native="someevent()"
name="lineup_id"
v-model="$store.state.form.id" label="Select Id"
:options="options"
/>
</div>
</template>
import AppSelect from "../AppSelect.vue";
export default {
data() {
return {
options:[]
};
},
components: {
AppSelect,
},
}
So I want to fire change event from child "a"
"X" and "Y" any parent get that event and do something with that event
I know how to do it with VUE
Fist : Don't use $state to get properties, use mapState
<template>
<div>
<app-select
#change.native="someevent()"
name="lineup_id"
v-model="explicitPropertyName" label="Select Id"
:options="options"
/>
</div>
</template>
<script>
import AppSelect from "../AppSelect.vue";
import { mapState } from 'vuex'
export default {
computed : {
...mapState(['explicitPropertyName'])
}
data() {
return {
options:[]
};
},
components: {
AppSelect,
},
}
</script>
Second : Use bus event, see an example
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.prototype.$eventBus = new Vue()
const componentA = {
template : `<button #click="emitMethod">Component A emit</button>`,
methods: {
emitMethod () {
this.$eventBus.$emit('EVENT_NAME_FROM_A', { id: 12, pseudo: "Unicorn power A"});
}
},
mounted () {
this.$eventBus.$on('EVENT_NAME_FROM_B', function (payload) {
console.log('Emitted from component B, received in Component A', payload);
});
}
}
const componentB = {
template : `<button #click="emitMethod">Component B emit</button>`,
methods: {
emitMethod () {
this.$eventBus.$emit('EVENT_NAME_FROM_B', { id: 12, pseudo: "Unicorn power"});
}
},
mounted () {
this.$eventBus.$on('EVENT_NAME_FROM_A', function (payload) {
console.log('Emitted from component A, received in Component B', payload);
});
}
}
const vm = new Vue({
el: "#app",
components : {
componentA,
componentB
},
mounted () {
this.$eventBus.$on('EVENT_NAME_FROM_B', function (payload) {
console.log('Emitted from component B, received in Parent', payload);
});
this.$eventBus.$on('EVENT_NAME_FROM_A', function (payload) {
console.log('Emitted from component A, received in Parent', payload);
});
}
})
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="app">
<div>
<component-b></component-b>
<component-a></component-a>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</body>
</html>
I have a add-user.vue component. It is my template for adding a new user and editing a existing user. So on page load I check if route has a id, if so I load a user from a state array to edit it. My issue is that user is undefined because the state array users is empty. How can I ensure that my user object isn't undefined. It does load sometimes but on refresh it doesn't. I thought I had it covered but nope. This is my setup. What am I missing here?
Store
state: {
users: []
},
getters: {
users: state =>
_.keyBy(state.users.filter(user => user.deleted === false), 'id')
},
actions: {
fetchUsers({
commit
}) {
axios
.get('http://localhost:3000/users')
.then(response => {
commit('setUsers', response.data);
})
.catch(error => {
console.log('error:', error);
});
}
}
In my add-user.vue component I have the following in the data() computed:{} and created()
data() {
return {
user: {
id: undefined,
name: undefined,
gender: undefined
}
};
},
computed: {
...mapGetters(['users'])
},
created() {
if (this.$route.params.userId === undefined) {
this.user.id = uuid();
...
} else {
this.user = this.users[this.$route.params.userId];
}
}
Template
<template>
<div class="add-user" v-if="user"></div>
</template>
My User.vue I have the following setup, where I init the fetch of users on created()
<template>
<main class="main">
<AddUser/>
<UserList/>
</main>
</template>
<script>
import AddUser from '#/components/add-user.vue';
import UserList from '#/components/user-list.vue';
export default {
name: 'User',
components: {
AddUser,
UserList
},
created() {
this.$store.dispatch('fetchUsers');
}
};
</script>
I have tried this. When saving in my editor it works but not on refresh. The dispatch().then() run before the mutation setting the users does.
created() {
if (this.$route.params.userId === undefined) {
this.user.id = uuid();
...
} else {
if (this.users.length > 0) {
this.user = this.users[this.$route.params.userId];
} else {
this.$store.dispatch('fetchUsers').then(() => {
this.user = this.users[this.$route.params.userId];
});
}
}
}
I would use beforeRouteEnter in User.vue so that the component is not initialized before the data is loaded.
(Assuming you are using vue-router)
beforeRouteEnter (to, from, next) {
if (store.state.users.length === 0) {
store.dispatch(fetchUsers)
.then(next);
}
},
You'll need to
import store from 'path/to/your/store'
because this.$store is not available until the component is initialized.
Although this solved, but I will answer for future comers. You may issue the dispatch beforehand as Shu suggested, or you may still dispatch on same component mounted hook, but use some state variable to track the progress:
data:{
...
loading:false,
...
},
...
mounted(){
this.loading = true,
this.$store
.dispatch('fetchUsers')
.finally(() => (this.loading=false));
}
Then in your template you use this loading state variable to either render the page or to render some spinner or progress bar:
<template>
<div class='main' v-if="!loading">
...all old template goes her
</div>
<div class="overlay" v-else>
Loading...
</div>
</template>
<style scoped>
.overlay {
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
color: #FFFFFF;
}
</style>
For this particular case it was just going back and forth to the same vue instance. Solved it by adding :key="some-unique-key", so it looks like this.
<template>
<main class="main">
<AddUser :key="$route.params.userId"/>
<UserList/>
</main>
</template>
I am trying to integrate Phaser 3 with Vue.js 2.
My goal is to create a Vue.js component associated with a game canvas.
My initial solution was:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
mounted () {
this.id = 'game' + this._uid
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
This code attach the game canvas to my template. But to my surprise it only worked 'sometimes'.
I figured out, after hours of debugging, that my div element in the DOM wasn't updated with the id when I was instantiating my new Game.
So I came up with the solution of instantiating the id in the beforeMount () method as follow:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
beforeMount () {
this.id = 'game' + this._uid
},
mounted () {
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
It is working, but I would like to know if there is a more simple and elegant solution ?
One better solution for integrating Phaser.Game into the application is directly passing the config the HTML element, a configuration supported by Phaser.Game.
To get a reference to a HTML element in vue, you can use refs, these are basically id's, but local to the component itself, so there is not risk in creating conflicts.
<template>
<div ref="myDiv">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
game: null
}
},
mounted () {
var config = {
parent: this.$refs.myDiv,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
Vue3 sample:
<script setup>
import { ref,onMounted } from 'vue';
import Phaser from 'phaser'
const myDiv = ref(null)
let canvasWidth = 750;
let canvasHeight = 1450;
onMounted(() => {
const config = {
type: Phaser.AUTO,
parent: popWrap.value,
width: canvasWidth,
height: canvasHeight,
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
})
</script>
<template>
<div ref="myDiv">
</div>
</template>
Context
In Vue 2.0 the documentation and others clearly indicate that communication from parent to child happens via props.
Question
How does a parent tell its child an event has happened via props?
Should I just watch a prop called event? That doesn't feel right, nor do alternatives ($emit/$on is for child to parent, and a hub model is for distant elements).
Example
I have a parent container and it needs to tell its child container that it's okay to engage certain actions on an API. I need to be able to trigger functions.
Vue 3 Composition API
Create a ref for the child component, assign it in the template, and use the <ref>.value to call the child component directly.
<script setup>
import {ref} from 'vue';
const childComponentRef = ref(null);
function click() {
// `childComponentRef.value` accesses the component instance
childComponentRef.value.doSomething(2.0);
}
</script>
<template>
<div>
<child-component ref="childComponentRef" />
<button #click="click">Click me</button>
</div>
</template>
Couple things to note-
If your child component is using <script setup>, you'll need to declare public methods (e.g. doSomething above) using defineExpose.
If you're using Typescript, details of how to type annotate this are here.
Vue 3 Options API / Vue 2
Give the child component a ref and use $refs to call a method on the child component directly.
html:
<div id="app">
<child-component ref="childComponent"></child-component>
<button #click="click">Click</button>
</div>
javascript:
var ChildComponent = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue: function(value) {
this.value = value;
}
}
}
new Vue({
el: '#app',
components: {
'child-component': ChildComponent
},
methods: {
click: function() {
this.$refs.childComponent.setValue(2.0);
}
}
})
For more info, see Vue 3 docs on component refs or Vue 2 documentation on refs.
What you are describing is a change of state in the parent. You pass that to the child via a prop. As you suggested, you would watch that prop. When the child takes action, it notifies the parent via an emit, and the parent might then change the state again.
var Child = {
template: '<div>{{counter}}</div>',
props: ['canI'],
data: function () {
return {
counter: 0
};
},
watch: {
canI: function () {
if (this.canI) {
++this.counter;
this.$emit('increment');
}
}
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
data: {
childState: false
},
methods: {
permitChild: function () {
this.childState = true;
},
lockChild: function () {
this.childState = false;
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div id="app">
<my-component :can-I="childState" v-on:increment="lockChild"></my-component>
<button #click="permitChild">Go</button>
</div>
If you truly want to pass events to a child, you can do that by creating a bus (which is just a Vue instance) and passing it to the child as a prop.
You can use $emit and $on. Using #RoyJ code:
html:
<div id="app">
<my-component></my-component>
<button #click="click">Click</button>
</div>
javascript:
var Child = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue: function(value) {
this.value = value;
}
},
created: function() {
this.$parent.$on('update', this.setValue);
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
methods: {
click: function() {
this.$emit('update', 7);
}
}
})
Running example: https://jsfiddle.net/rjurado/m2spy60r/1/
A simple decoupled way to call methods on child components is by emitting a handler from the child and then invoking it from parent.
var Child = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue(value) {
this.value = value;
}
},
created() {
this.$emit('handler', this.setValue);
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
methods: {
setValueHandler(fn) {
this.setter = fn
},
click() {
this.setter(70)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<my-component #handler="setValueHandler"></my-component>
<button #click="click">Click</button>
</div>
The parent keeps track of the child handler functions and calls whenever necessary.
Did not like the event-bus approach using $on bindings in the child during create. Why? Subsequent create calls (I'm using vue-router) bind the message handler more than once--leading to multiple responses per message.
The orthodox solution of passing props down from parent to child and putting a property watcher in the child worked a little better. Only problem being that the child can only act on a value transition. Passing the same message multiple times needs some kind of bookkeeping to force a transition so the child can pick up the change.
I've found that if I wrap the message in an array, it will always trigger the child watcher--even if the value remains the same.
Parent:
{
data: function() {
msgChild: null,
},
methods: {
mMessageDoIt: function() {
this.msgChild = ['doIt'];
}
}
...
}
Child:
{
props: ['msgChild'],
watch: {
'msgChild': function(arMsg) {
console.log(arMsg[0]);
}
}
}
HTML:
<parent>
<child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
The below example is self explainatory. where refs and events can be used to call function from and to parent and child.
// PARENT
<template>
<parent>
<child
#onChange="childCallBack"
ref="childRef"
:data="moduleData"
/>
<button #click="callChild">Call Method in child</button>
</parent>
</template>
<script>
export default {
methods: {
callChild() {
this.$refs.childRef.childMethod('Hi from parent');
},
childCallBack(message) {
console.log('message from child', message);
}
}
};
</script>
// CHILD
<template>
<child>
<button #click="callParent">Call Parent</button>
</child>
</template>
<script>
export default {
methods: {
callParent() {
this.$emit('onChange', 'hi from child');
},
childMethod(message) {
console.log('message from parent', message);
}
}
}
</script>
If you have time, use Vuex store for watching variables (aka state) or trigger (aka dispatch) an action directly.
Calling child component in parent
<component :is="my_component" ref="my_comp"></component>
<v-btn #click="$refs.my_comp.alertme"></v-btn>
in Child component
mycomp.vue
methods:{
alertme(){
alert("alert")
}
}
I think we should to have a consideration about the necessity of parent to use the child’s methods.In fact,parents needn’t to concern the method of child,but can treat the child component as a FSA(finite state machine).Parents component to control the state of child component.So the solution to watch the status change or just use the compute function is enough
you can use key to reload child component using key
<component :is="child1" :filter="filter" :key="componentKey"></component>
If you want to reload component with new filter, if button click filter the child component
reloadData() {
this.filter = ['filter1','filter2']
this.componentKey += 1;
},
and use the filter to trigger the function
You can simulate sending event to child by toggling a boolean prop in parent.
Parent code :
...
<child :event="event">
...
export default {
data() {
event: false
},
methods: {
simulateEmitEventToChild() {
this.event = !this.event;
},
handleExample() {
this.simulateEmitEventToChild();
}
}
}
Child code :
export default {
props: {
event: {
type: Boolean
}
},
watch: {
event: function(value) {
console.log("parent event");
}
}
}