Being create Menu components use to frameless windows. but I don't know how to bind events into slot. I want to build structure like this
in WindowBar.vue
<template>
<div>
<app-menu-bar>
<app-menu label="File"></app-menu>
</app-menu-bar>
</div>
</temaplte>
in AppMenuBar.vue (name is app-menu-bar)
<template>
<section>
<slot
v-bind:onSelected="onSelected"
v-on:doClick="doClick"
>
</slot>
</section>
</template>
<script>
export default {
name: 'app-menu-bar',
data () {
return {
onSelected: ''
}
}
}
</script>
in AppMenu.vue (name is app-menu)
<template>
<section class="menu">
<button
v-on:click="doClick"
v-bind:class="isSelected"
>
{{ label }}
</button>
</section>
</template>
<script>
export default {
name: 'app-menu',
props: {
label: String
},
computed: {
isSelected () {
// This is binding data from AppMenuBar
return this.onSelected == this.label ? 'selected' : ''
}
},
methods: {
doClick () {
this.$emit('doClick', this.label)
}
},
mounted () {
console.log(this.onSelected) // it is undefined
}
}
</script>
how do i binding data into slot, like this.onSelected?
You can't listen to events on <slot>. The more detailed answer you can read here https://github.com/vuejs/vue/issues/4332.
In your case, I will suggest putting all business logic in the parent component (which is WindowBar.vue). After that, you can have control of your child's component events and props. Or, if there is some specific situation, and you want to pass events through nested components you can use the event bus for it. More about the event bus you can read here: https://www.digitalocean.com/community/tutorials/vuejs-global-event-bus
I have the main component that containing other components which containing anothers components.
So, I have the click events in these components, but to handle it in my parent component, I need to make $emit call in each nested component.
How to make this process more simple, for example like in React, where I need just pass the function handler into component.
In vue 2.2.3+ you can use provide and inject to pass function from ancestor component to child, like great grandparent to child.
please refer following code
// app.vue
<template>
<div id="app">
<HelloWorld msg="button1" />
<HelloWorld msg="button2" />
<HelloWorld msg="button3" />
count: {{ count }}
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
provide() {
return {
clickHandler: this.clickHandler,
};
},
data() {
return {
count: 0,
};
},
components: {
HelloWorld,
},
methods: {
clickHandler() {
this.count += 1;
console.log("click received");
},
},
};
</script>
// HelloWorld.vue
<template>
<button #click="clickHandler">{{ msg }}</button>
</template>
<script>
export default {
name: "HelloWorld",
inject: ["clickHandler"],
props: {
msg: String,
},
};
</script>
you can see the same clickHandler function from parent is executed with modifying parents count prop on click of each children.
this clickHandler can be injected directly to any descendent at any level therefore application like
parent > child.1 > child.1.1 > child.1.1.1 > child.1.1.1.1(click)
the child.1.1.1.1 can be injected with clickHandler form parent.
try the code at codesandbox
also refer provide/inject
if you need the same value up in the hierarchy or anywhere in the current module, you should try to use the Vuex(State Management) library.
I'm making a todo app in vue.js which has a component TodoItem
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo"],
methods: {
markCompleted() {
this.todo.completed = true
},
},
};
</script>
todo prop that I'm passing:
{
id:1,
task:'todo 1',
completed:false
}
but it is throwing an error error Unexpected mutation of "todo" prop
Method 1 (Vue 2.3.0+) - From your parent component, you can pass prop with sync modifier
Parent Component
<TodoItem v-for="todo in todoList" :key="todo.id" todo_prop.sync="todo">
Child Component
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo_prop"],
data() {
return {
todo: this.todo_prop
}
},
methods: {
markCompleted() {
this.todo.completed = true
},
},
};
</script>
Method 2 - Pass props from parent component without sync modifier and emit an event when the value changed. For this method, everything else is similar as well. Just need to emit an event when the todo item changed to completed.
The code is untested. Apologies if anything does not work.
What happen ? : Mutating a prop locally is now considered an anti-pattern, e.g. declaring a prop and then setting this.myProp = 'someOtherValue' in the component. Due to the new rendering mechanism, whenever the parent component re-renders, the child component’s local changes will be overwritten.
Solution : You can storage it as local data.
export default {
name: "TodoItem",
props: ["todo"],
data() {
return {
todoLocal: this.todo,
};
},
methods: {
markComplete() {
this.todoLocal.completed = !this.todoLocal.completed;
},
},
};
For me to fix this problem I store props in todos data im watching brad vue tutorials and i get this error this is my actual codes and its working.
<template>
<div class="todo-item" v-bind:class="{ 'is-complete': todo.completed }">
<p>
<input
type="checkbox"
v-on:change="markComplete(todo.completed)"
v-bind:checked="todo.completed"
/>
{{ todo.title }}
<!-- <button #click="$emit('del-todo', todo.id)" class="del">x</button> -->
</p>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props: ['todo'],
data() {
return {
todos: this.todo,
}
},
methods: {
markComplete(isComplete) {
this.todos.completed = !isComplete
},
},
}
</script>
<style scoped>
.todo-item {
background: #f4f4f4;
padding: 10px;
border-bottom: 1px #ccc dotted;
}
.is-complete {
text-decoration: line-through;
}
.del {
background: #ff0000;
color: #fff;
border: none;
padding: 5px 9px;
border-radius: 50%;
cursor: pointer;
float: right;
}
</style>
One of the core principles of VueJS is that child components never mutate a prop.
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around.
If you wish to have the child component update todo.completed, you have two choices:
Use .sync modifier (Recommended)
This approach will require a bit of change to your props. You can read more about it here.
Parent component
<template>
<div>
...
<todo-item :task="nextTodo.task" :completed.sync="nextTodo.completed"/>
</div>
</template>
Child component
<template>
<div class="todo-item" v-bind:class="{'is-completed':completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["task", "completed"],
methods: {
markCompleted() {
this.$emit('update:completed', true)
},
},
};
</script>
Use a custom event
Vue allows you set up listeners in your parent for events that the child will emit. Your child component can use this mechanism to ask the parent to change things. In fact, the above .sync modifier is doing exactly this behind the scenes.
Parent component
<template>
<div>
...
<todo-item :todo="nextTodo" #set-completed="$value => { nextTodo.completed = $value }/>
</div>
</template>
Child component
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo"],
methods: {
markCompleted() {
this.$emit('set-completed', true)
},
},
};
</script>
You can't change a prop from inside a component - they are meant to be set by the parent only. It's a one-directional communication path.
You can try one of two things - either move your logic for detecting a todo has been completed to the parent, or feed the prop into a new variable in the data() lifecycle hook (this will only happen when the component is loaded for the first time, so you won't be able to update from outside the component, if that's important for your use case).
The canonical way to achieve n-deep prop binding in Vue 3 is to wrap your prop with a simple computed property. This is an example of a component that will communicate changes to it's selected property to it's parent--who is ultimately responsible for storing the state.
<template>
<!-- In this example my-child-component also has a "selected" prop -->
<my-child-component v-model:selected="syncSelectedId" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
props: {
selected: {
type: String,
required: true,
}
},
emits: ['update:selected'],
setup(props, context) {
const syncSelectedId = computed<string>({
get() {
return props.selected;
},
set(newVal: string) {
context.emit('update:selected', newVal);
},
});
return {
syncSelectedId,
}
}
});
So to re-iterate: With this strategy the highest level parent is the holder of the state. The code above assumes that there is a parent component in the hierarchy (so this component is just a "middle-man").
Then my-child-component can simply emit its own update:selected event to cause the state to change. That child will be updated appropriately through it's prop after the emit event causes the parent chain to propagate that change up (through emits) and then back down the component hierarchy (through props).
If you wanted to you could modify the code above to make it the "owner" of the state:
<template>
<my-child-component v-model:selected="selected" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
setup(props, context) {
const selected = ref('');
return {
selected,
}
}
});
And now of course you won't run into the "Unexpected mutation of X prop" error.
Another option is to have a prop that serves as a "default value" for a given state:
<template>
<my-child-component v-model:selected="selected" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
props: {
defaultSelected: {
type: String,
required: false,
default: ''
}
},
setup(props, context) {
const selected = ref(props.defaultSelected);
return {
selected,
}
}
});
And in this code above keep in mind that selected will NOT change if defaultSelected changes after the component has been initialized.
And lastly it's worth noting that you could write more sophisticated code to detect if a property is supplied--and if not use an internal state variable to store the value. I use this pattern for re-usable components that could be embedded in places where the parent wants to control the state OR in places where the parent is happy to delegate the storage of the state to the child:
<template>
<!-- In this example my-child-component also has a "selected" prop -->
<my-child-component v-model:selected="syncSelectedId" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
props: {
selected: {
type: String,
required: false,
default: null // Important: parent MUST pass non-null value if it wants to control state
}
},
emits: ['update:selected'],
setup(props, context) {
// This is state storage used if prop.selected is not provided
const _selected = ref('');
const syncSelectedId = computed<string>({
get() {
return props.selected === null ? _selected.value : props.selected;
},
set(newVal: string) {
if (props.selected !== null) {
// Using prop.selected as the driving model...
if (newVal !== props.selected) {
// We need to set to empty string (never null)
context.emit('update:selectedId', (newVal == null ? '' : newVal));
}
} else { // Storing selection state with _selectedId
if (newVal !== _selected.value) {
_selected.value = newVal == null ? '' : newVal;
context.emit('update:selected', _selected);
}
}
},
});
return {
syncSelectedId,
}
}
});
This last example is tricky... it gives special meaning to null and requires that you be very mindful of potential values of your state. In my example empty string is my representation for "no selection" and null is used as a flag for "no parent model of this state".
Mainly, property mutation is now deprecated and parent properties are overwritten when the parent component renders its DOM.
Here's the official documentation about it. We can still achieve this in multiple possible ways. Through a data property, a computed property, and component events.
When we want to pass this value back to the parent component as well as the nested child component of the current child component, using a data property would be useful as shown in the following example.
Example:
Calling your child component from the parent component like this.
Parent component:
<template>
<TodoItem :todoParent="todo" />
</template>
<script>
export default {
data() {
return {
todo: {
id:1,
task:'todo 1',
completed:false
}
};
}
}
</script>
Child component:
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todoParent"],
data() {
return {
todo: this.todoParent,
};
},
methods: {
markCompleted() {
this.todo.completed = true
},
},
};
</script>
Even you can pass this property to the nested child component and it won't give this error/warning.
Other use cases when you only need this property sync between parent and child component. It can be achieved using the sync modifier from Vue. v-model can also be useful. Many other examples are available in this question thread.
Example2: using component events.
We can emit the event from the child component as below.
Parent component:
<template>
<TodoItem :todo="todo" #markCompletedParent="markCompleted" />
</template>
<script>
export default {
data() {
return {
todo: {
id:1,
task:'todo 1',
completed:false
}
};
},
methods: {
markCompleted() {
this.todo.completed = true
},
}
}
</script>
Child component:
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo"],
methods: {
markCompleted() {
this.$emit('markCompletedParent', true)
},
}
};
</script>
While you can still custom-bind events to handle this, .sync property extensions are considered deprecated. In Vue3 (at least) you can and usually should use the v-model:property declaration, similar to how you bind the property to the actual input. You just need to bind the inner input with :value and have it emit a matching update:property
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
/>
</template>
And use thusly:
<CustomInput v-model="searchText" />
I am trying to modify a property routing of a parent component from a child as follows:
//- Parent
<template>
<First #toggleContent="routing = !routing" />
</template>
<script>
export default {
data() {
return { routing: true }
}
}
</script>
//- Child (First component)
<template>
<div>
<i class="bx bx-shape-triangle" #click="toggleContent()"></i>
{{routing}}
</div>
</template>
<script>
export default {
props: {
routing: Boolean
},
methods: {
toggleContent() {
this.$emit('toggleContent')
}
}
}
</script>
on the console does not mark me any error.
Your child component requires a prop routing that needs to passed in from parent component, also keep in mind always using kebab case for your events.
<First #toggle-content="routing = !routing" :routing="routing"/>
Modify your event name in emit as well:
toggleContent() {
this.$emit('toggle-content')
}
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(() => {