Parent variable not updated when updating trough child component - vue.js

I am trying to create a few custom form fields for my page and i learned that i cannot use props to do so so i am trying to find a way to update my parent component variable when i use my child component. Whe i check the parent variable it is always empty.
Here is my component:
<template>
<input
v-model="value"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder'],
data() {
return {
value: ''
}
},
methods: {
updateValue(){
this.$emit("update-text", this.value);
}
},
watch: {
value: function(){
this.updateValue
}
}
}
</script>
And this is how i use the component:
<TextField placeholder="Nome" :update-text="name = value"/>
what exactly am i doing wrong?
I am using vue.js with nuxt.js

I think a simpler approach in this case might be emitting an input event from your custom text field and binding the component to the variable using v-model.
TextField.vue
<template>
<input
#input="$emit('input', $event.target.value)"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder']
}
</script>
Usage
<template>
<TextField placeholder="Nome" v-model="name"/>
</template>
<script>
export default {
data: () => ({
name: '',
}),
}
</script>
Read more about using v-model on custom components here.

Related

Why do I need the v-bind when I have the v-on?

In the tutorial of vue.js, we have this code
<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>
<template>
<input :value="text" #input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>
And I don't understand why when I delete the bind on value, the two way binding is still working ?
In the tuto, it says that using the v-on & v-bind allow to do two way binding
Am I missing something ?
The Vue example is sort of a bad use case, a little simple for what it's trying to convey:
v-on is for assigning event listeners, so v-on:click="doSomething(value)"
v-bind is binding the actual value of vue data/state. So example:
<button v-on:click="setUserDetails(value)" v-bind:value="user.id">Click</button>
Imagine this component:
<template>
<input :value="value"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
And now a simple usage of it:
<template>
<MyComp v-model="passwd" type="password" minlength="3" #focus="onFocus"/>
</template>
<script>
export default {
name: 'MyOtherComp',
data(){
return {
passwd: ''
}
},
methods:{
onFocus(){}
}
}
</script>
As you can see, value, type, and minlength properties and focus event are bidden to MyComp.
Now question: How can I handle extra props in MyComp? they are not defined in MyComp props. Vue gathers them in a special variable called $attrs, which is a normal JS object. Vue also gathers all events into $listeners variable.
Now inside MyComp these special variables are:
$atrrs:{
type: 'password',
minlength: '3'
}
$listerners:{
focus: /* function onFocus from parent */
}
To redirect these values:
<template>
<input :value="value" v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
As you can see, we use v-bind to bind extra props, and we use v-on to bind (redirect) events. The result is:
<input :value="value" :type="$attrs.type" :minlength="$attrs.minlength" #focus="$listeners.focus"/>
Of course you can use these directions to bind you objects too:
<template>
<input :value="value" v-bind="$attrs" v-bind="accumulated" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
},
data(){
return {
accumulated:{
maxlenght: (+this.$attrs.minlength || 2) + 30, // It's just for a practice to use extra props inside JS code :-)
rows: 5,
}
}
}
}
</script>
Keep in mind that duplicate props will replace and the last one wins.

error Unexpected mutation of "todo" prop in vue.js (I'm using vue3)

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

Vue - v-model inside a component within a component

I'm trying to separate my project now into components to make the code readable when adjusting into a responsive app. The problem is passing the v-model from base-select -> child -> parent. How do I store the data selected to the Parent.vue items: ''? Here is my code below.
Parent.vue
<template>
<child></child>
</template>
<script>
import Child from './components/Child'
export default {
components: {
Child,
},
data: ()=> ({
item: ''
})
}
</script>
Child.vue
<template>
// Random HTML
// Random HTML 2
<base-select
:items="select"
>
</template>
<script>
import BaseSelect from '#/components/BaseSelect'
export default {
components: {
BaseSelect,
},
data: ()=> ({
select: ['Select 1', 'Select 2']
})
}
</script>
BaseSelect.vue
<template>
<v-select
v-bind="$attrs"
v-on="$listeners"
class="body-2"
solo
dense
clearable
/>
</template>
To implement v-model you need to add a value property to each child component. Each component will also need to emit an input event so that the parent component can pick up the change (read more here). Note that if you are passing data down through too many components, you should probably look at using Vuex however in this case it would probably still be fine.
Your components would have to look something like this to pass v-model all the way to the base component:
Parent.vue
<template>
<!-- Pass the data item below -->
<child v-model="item"></child>
</template>
<script>
import Child from './components/Child'
export default {
components: {
Child,
},
data: ()=> ({
item: ''
})
}
</script>
Child.vue
<template>
// Random HTML
// Random HTML 2
<base-select
:items="select"
value="value"
#input="e => $emit('input', e)"
>
</template>
<script>
import BaseSelect from '#/components/BaseSelect'
export default {
components: {
BaseSelect,
},
// We add the value prop below to work with v-model
props: {
value: String
},
data: ()=> ({
select: ['Select 1', 'Select 2']
}),
}
</script>
BaseSelect.vue
<template>
<v-select
v-bind="$attrs"
v-on="$listeners"
value="value"
#input="e => $emit('input', e)"
class="body-2"
solo
dense
clearable
/>
</template>
<script>
export default {
props: {
value: String
}
}
</script>
You can find a similar working example that I did here.
You need to use $emit (documentation) to passing data back to parent components. Or you can start using Vuex (state manager for Vue.js).
You also can check the live demo here.

How to use v-model and props passed to parent from child?

I've been learning vue for a few days now and I'm trying out passing data/props between child and parent.
Now I have the following child:
<template>
<div>
<input v-model="name1" placeholder="string">
<input v-model="number1" placeholder="number">
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
}
}
</script>
And then parent:
<template>
<div>
<child/>
</div>
</template>
<script>
import child from "#/components/complexComponent4/child.vue"
export default{
name: "parent",
components: {
child
}
}
</script>
Now when I enter some text into the input fields, it displays correctly in the paragraphs, since the props bound to the paragraphs have changed.
However, I get this warning:
[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: "name1"
found in
---> <Child>
<Parent> at src/components/complexComponent4/parent.vue
<MyComplexView4.vue> at src/views/myComplexView4.vue
<App> at src/App.vue
<Root>
I read about this error in multiple places on the internet and also in the documentation, and I found that mutating props is deemed an anti-pattern now:
https://michaelnthiessen.com/avoid-mutating-prop-directly
Unfortunately I didn't really find anything specific and/or helpful on how to deal with this problem. Especially in context of vue handling primitive data and objects/arrays differently (objects/arrays are passed by reference).
v-model seems to play an important role in leveraging the power of vue, since it enables two-way binds. Therefore I wouldn't want to omit it entirely, unless its use has become so difficult that it doesnt justify the gains.
As the warning says, you should avoid mutating props directly in child component.
So you should emit an event from child to parent to let parent know that prop value had been changed. Parent will change the prop and pass it down to child.
For such purpose there is a syntactic sugar in Vue called .sync modifier.
So your components could be something like this.
Child:
<template>
<div>
<input
:value="name1"
#change="$emit('update:name1', $event.target.value)"
placeholder="string"
/>
<input
:value="number1"
#change="$emit('update:number1', $event.target.value)"
placeholder="number"
/>
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
}
}
</script>
And parent:
<template>
<div>
<child :name1.sync="name1" :number1.sync="number1"/>
</div>
</template>
<script>
import child from "#/components/complexComponent4/child.vue"
export default{
name: "parent",
components: {
child
},
data() {
return {
name1: '',
number1: ''
}
}
}
</script>
Or for more complicated cases you can either use v-model and computed properties with setters in child component:
<template>
<div>
<input
v-model="computedName1"
placeholder="string"
/>
<input
v-model="computedNumber1"
placeholder="number"
/>
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
},
computed: {
computedName1: {
get() { return this.name1 },
set(value) {
// some logic
this.$emit('update:name1', value)
},
computedNumber1: {
get() { return this.number1 },
set(value) {
// some logic
this.$emit('update:number1', value)
}
}
}
}
</script>
If you intend to change prop passed down to child assign it first to child data.
<template>
<div>
<input v-model="name" placeholder="string">
<input v-model="number" placeholder="number">
<p v-text="name"></p>
<p v-text="number"></p>
</div>
</template>
<script>
export default {
name: "child",
data() {
return {
name: null,
number: null
}
},
props: {
name1 : String,
number1 : Number
},
mounted() {
this.name = this.name1;
this.number = this.number1;
}
}
</script>
When data is changed you can $emit those changes to parent component
With sync
Parent
<child :number1.sync="number1" :name1.sync="name1" />
Child
watch: {
name: value => this.$emit('update:name1', value)
number : value => this.$emit('update:number1', value)
},
With events
Parent
<child :number1="number1" :name1="name1" #changeNumber="value => number1 = value" #changeName="value => name1 = value" />
Child
watch: {
name: value => this.$emit('changeName', value)
number : value => this.$emit('updateNumber', value)
},
A guideline for vue.js is that you can use props to automatically alter data in the child from the parent, but not vice-versa. For altering data of the parent-component, the child-component is supposed to use events. You could consider using two different components for name1 and number1 respectively and bind the values in a two-way-manner by making these components applicable for v-model, as it is described here.

How to bind a prop value using a function and pass it to another component

Is it possible to bind a prop to a function?
In my example below I’m trying to get a value from a function in the main App.vue and pass it as a prop to the child component customComponent.
e.g. (this example doesn’t work)
App.vue
import customComponent from ‘./custom-component.vue'
<template>
<custom-component
v-bind:myValue="geMyValue()"
></custom-component>
</template>
<script>
export default {
name: "Item",
methods: {
getMyValue: function() {
return 1+3;
}
}
}
</script>
customComponent.vue
<template>
<h3 class="some-custom-layout">custom component</h3>
<input type="button" #click="sendMyValue()" />
</template>
<script>
export default {
name: “custom",
props: ['myValue']
methods: {
sendMyValue: function() {
console.log(this.myValue);
}
}
}
</script>
It's possible, but probably it would be better to use computed properties, if you are going to return value:
<template>
<custom-component
v-bind:myValue="myValue"
></custom-component>
</template>
<script>
export default {
name: "Item",
computed: {
myValue: function() {
return 1+3;
}
}
}
</script>
https://v2.vuejs.org/v2/guide/computed.html