I have created simple component for button in vuejs,When I clicked on the button then the text should show "I have been clicked". and it does but with this I also getting error that say: 49:7 error Unexpected mutation of "buttonText" prop vue/no-mutating-props
I have tried with this.$emit() but didnot mange to fix the mutation error issue. Can someone explain me what I am doing wrong and why I cannot mutate the buttonText props. Any help will be much appreciated!
here is my code:
App.vue
<template>
<div>
<ButtonDisabled buttonText="ChangedText" />
</div>
</template>
ButtonDisabled.vue:
<template>
<div class="main-container">
<h1>Button Disable if nothing is enter in email field</h1>
<div class="wrapper">
<input type="email" placeholder="email" v-model="email" />
<button :disabled="email.length < 1" class="btn-sub" #click="handleClick">
Subcribe
</button>
<!-- <button :disabled="!email.length">Subscribe</button> -->
<h2>{{ email }}</h2>
<h1>{{ buttonText }}</h1>
</div>
</div>
</template>
<script>
export default {
name: "ButtonDynamic",
props: {
buttonText: {
type: String,
default: "clickMe",
required: false,
}
},
data() {
return {
email: "",
};
},
methods: {
handleClick() {
// this.$emit("change-text-onclick", this.buttonText);
this.buttonText = "I have been clicked!!!";
},
},
};
</script>
<style scoped>
.main-container {
width: 300px;
height: 150px;
margin: 0 auto;
}
.wrapper {
display: inline-flex;
flex-direction: column;
}
.btn-sub {
margin: 10px;
}
</style>
Getting this error:
But my component still works when clicked:
Is it a bad practice to mutate a prop see link, you can mutate an inner component variable inside data or use v-model to have two-way dataflow on your props using v-model.
in this case, you can:
set up your internal data variable with the value of your prop, and then mutate that data variable ( not the prop )
Create and set the prop as a model value and expose it with a emit.
I will show you the easier path to get out of the error, but you need to reconsider your component responsibilities.
Vue.component('mybutton', {
template: `
<div class="main-container">
<h1>Button Disable if nothing is enter in email field</h1>
<div class="wrapper">
<input type="email" placeholder="email" v-model="email" />
<button :disabled="email.length < 1" class="btn-sub" #click="handleClick">
Subcribe
</button>
<!-- <button :disabled="!email.length">Subscribe</button> -->
<h2>{{ email }}</h2>
<h1>{{ inputMessage }}</h1>
</div>
</div>
`,
props: {
buttonText: {
type: String,
default: "clickMe",
required: false,
}
},
data() {
return {
email: "",
inputMessage: this.buttonText
};
},
methods: {
handleClick() {
this.inputMessage = "I have been clicked!!!";
},
},
});
new Vue({
el: '#app',
data(){
return {
message: "Changed Text"
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<mybutton button-text="Change Text" />
</div>
</div>
Related
I have component child Input:
<template>
<div class="basic-input-outer" :style="styles">
<p class="paragraph-small">{{ title }}</p>
<input ref="name" :type="type" class="basic-input">
</div>
</template>
<script>
export default {
name: "Input",
props: ['type', 'styles', 'title', 'focus'],
watch: {
focus: function() {
this.setFocus()
}
},
methods: {
setFocus() {
this.$refs.name.focus()
}
}
}
</script>
<style lang="scss">
#import "../assets/css/components/Input";
</style>
And I have parent component where I work with this input:
<div class="login-options">
<p class="choose" #click="chooseLogin('email')">With Email</p>
<div class="vertical-line" />
<p class="choose" #click="chooseLogin('phone')">With Phone Number</p>
</div>
<div v-if="loginWithEmail">
<Input :focus="emailFocus" :title="'Email'" :type="'email'" />
</div>
<div v-else>
<Input :focus="phoneFocus" :title="'Phone number'" :type="'email'" />
</div>
...
chooseLogin(option) {
if (option === 'email') {
this.loginWithEmail = true
this.emailFocus = true
} else {
this.loginWithEmail = false
this.phoneFocus = true
}
}
So, the problem is, when I trigger the function it only focuses one time on one field, and then stops. I want to make that focus props works that way, so when it's triggered, the field will be focused, and it will be working not just one time, like in this case.
Here is solution. Actually, it was because v-if. v-if just remove element(-s) from DOM, so, the best way to solve such problems is to use conditional styling with display: none:
Input:
<template>
<div class="basic-input-outer" :style="styles">
<p class="paragraph-small">{{ title }}</p>
<input ref="name" :type="type" class="basic-input">
</div>
</template>
<script>
export default {
name: "Input",
props: ['type', 'styles', 'title', 'focus'],
watch: {
focus: function() {
if (this.focus) this.$refs.name.focus()
}
}
}
</script>
<style lang="scss">
#import "../assets/css/components/Input";
</style>
And parent component:
<Input :style="[!loginWithEmail ? {'display': 'none'} : {'': ''}]" :focus="emailFocus" :title="'Email'" :type="'email'" />
<Input :style="[loginWithEmail ? {'display': 'none'} : {'': ''}]" :focus="phoneFocus" :title="'Phone number'" :type="'email'" />
...
chooseLogin(option) {
if (option === 'email') {
this.loginWithEmail = true
this.emailFocus = true
this.phoneFocus = false
} else {
this.loginWithEmail = false
this.emailFocus = false
this.phoneFocus = true
}
}
I have a custom component called HobbyForm which is a simple form with two controls, a checkbox and an input, this component is being called from a parent component called Content, along with other similar 'form' components.
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
data() {
return {
hobby: {
isActive: false,
hobby: null
}
}
},
}
</script>
My Content component is something like:
<template>
<language-form></language-form>
<hobby-form v-for="(hobbie, index) in hobbies" :key="index" v-bind="hobbies[index]"></hobby-form>
<Button label="Add Hobby" #click="addHobby"></Button>
</template>
<script>
export default {
name: "Content",
components: {
LanguageForm,
HobbyForm
},
data() {
return {
language: '',
hobbies: [
{
isActive: false,
hobby: null
}
]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
};
</script>
The idea is to be able to add more instances of the HobbyForm component to add another hobby record to my hobby data property; but I don't know how to keep track of these values from my parent without using an emit in my child components, since I don't want to manually trigger the emit, I just want to have the data updated in my parent component.
How should I access my child component's data from my parent and add it to my array?
In the current form passing parent data into a child component via v-bind="hobbies[index]" makes no sense as the child component (HobbyForm) has no props so it does not receive any data from the parent...
To make it work:
Remove data() from the child HobbyForm
Instead declare a prop of type Object
Bind form items to the properties of that Object
Pass the object into each HobbyForm
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
props: {
hobby: {
type: Object,
required: true
}
}
}
</script>
Even tho props are designed to be one way only so child should not mutate prop value, this is something else as you do not mutate prop value, you are changing (via a v-model) the properties of the object passed via a prop (see the note at the bottom of One-Way Data Flow paragraph)
Also change the parent to:
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
Demo:
const app = Vue.createApp({
data() {
return {
hobbies: [{
isActive: false,
hobby: null
}]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
})
app.component('hobby-form', {
props: {
hobby: {
type: Object,
required: true
}
},
template: `
<form>
<div class="row align-items-center">
<div class="col-1">
<input type="checkbox" id="isHobbyActive" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<input type="text" id="hobby" placeholder="Hobby" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
`
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.1.5/dist/vue.global.js"></script>
<div id='app'>
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
<button #click="addHobby">Add Hobby</button>
<hr/>
<pre> {{ hobbies }} </pre>
</div>
Hi everyone i have a big trouble i need to modify the value of checkbox from parent to another parent to child , is not this difficulty i know but i'm at the start and after spent 2 days for this i can't try anything else , now i'll add all the code the child is named "UICheckbox" and i pass it to "ToDoListItem" and this last is passed to "ToDoList" , and every Todos is an in an array into "store.js"
UICheckbox
<template>
<div class="checkbox-container">
<input
type="checkbox"
:id="id"
:v-model="localTodo"
>
<label :for="id">
<p class="font-bold">{{ text }}</p>
</label>
</div>
</template>
<script>
export default {
props: [
"id",
"value",
"text"
],
data() {
return {
localTodo: this.value
}
},
watch: {
localTodo:{
handler(newVal, oldVal) {
this.$emit("change", newVal)
},
deep: true,
}
}
}
</script>
<style>
</style>
ToDoListItem
<template>
<div class="flex flex-row w-full my-2">
<UICheckbox
:v-model="localTodo.checked"
:id="todo.id"
:text="todo.text"
#change="localTodo.cheked = $event"
/>
<delete-button :todo="todo" />
</div>
</template>
ToDoList
<to-do-list-item
:todo="todo"
#click="changeChecked(todo)"
#change="todo.checked = $event"
/>
I have a problem with vue.js slots. On one hand I need to display the slot code. On the other hand I need to use it in a textarea to send it to external source.
main.vue
<template>
<div class="main">
<my-code>
<template v-slot:css-code>
#custom-css {
width: 300px
height: 200px;
}
</template>
<template v-slot:html-code>
<ul id="custom-css">
<li> aaa </li>
<li> bbb </li>
<li> ccc </li>
</ul>
</template>
</my-code>
</div>
</template>
my-code.vue
<template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"><slot name="css-code"></slot></component>
<slot name="html-code"></slot>
<!-- send the code -->
<form method="post" action="https://my-external-service.com/">
<textarea name="html">{{theHTML}}</textarea>
<textarea name="css">{{theCSS}}</textarea>
<input type="submit">
</form>
</div>
</template>
<script>
export default {
name: 'myCode',
props: {
codeId: String,
},
computed: {
theHTML() {
return this.$slots['html-code']; /* The problem is here, it returns vNodes. */
},
theCSS() {
return this.$slots['css-code'][0].text;
},
}
}
</script>
The issues is that vue doesn't turn the slot content. It's an array of <VNode> elements. Is there a way to use slots inside the textarea. Or a way to retrieve slot content in the theHTML() computed property.
NOTE: I use this component in vuePress.
You need to create a custom component or a custom function to render VNode to html directly. I think that will be the simplest solution.
vnode to html.vue
<script>
export default {
props: ["vnode"],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit(
"html",
[...this.$el.childNodes].map((n) => n.outerHTML).join("\n")
);
},
};
</script>
Then you can use it to your component
template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"
><slot name="css-code"></slot
></component>
<slot name="html-code"></slot>
<!-- send the code -->
<Vnode :vnode="theHTML" #html="html = $event" />
<form method="post" action="https://my-external-service.com/">
<textarea name="html" v-model="html"></textarea>
<textarea name="css" v-model="theCSS"></textarea>
<input type="submit" />
</form>
</div>
</template>
<script>
import Vnode from "./vnode-to-html";
export default {
name: "myCode",
components: {
Vnode,
},
props: {
codeId: String,
},
data() {
return {
html: "", // add this property to get the plain HTML
};
},
computed: {
theHTML() {
return this.$slots[
"html-code"
]
},
theCSS() {
return this.$slots["css-code"][0].text;
},
},
};
</script>
this thread might help How to pass html template as props to Vue component
I'm relatively new to Vue and super stuck with this error message when I try to make this reset email modal work in my Vue project:
Property or method "sendResetMail" is not defined on the instance but
referenced during render. Make sure that this property is reactive,
either in the data option, or for class-based components, by
initializing the property.
I have no idea what I need to do to make this work. I followed the Vue documentation and declared resetEmail in the data option.
ForgotPassword.vue:
<template>
<section>
<a #click="isComponentModalActive = true">
Forgot Password?
</a>
<b-modal :active.sync="isComponentModalActive" has-modal-card>
<modal-form v-bind="resetEmail"></modal-form>
</b-modal>
</section>
</template>
<script>
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'
const ModalForm = {
props: ['resetEmail'],
template: `
<form #submit.prevent="sendResetMail">
<div class="modal-card" style="width: auto">
<header class="modal-card-head">
<p class="modal-card-title">Forgot password?</p>
</header>
<section class="modal-card-body">
<b-field label="Email">
<b-input
type="email"
:value="resetEmail"
placeholder="Your email"
required>
</b-input>
</b-field>
</section>
<footer class="modal-card-foot">
<button class="button" type="button" #click="$parent.close()">Close</button>
<button class="button is-primary">Reset</button>
</footer>
</div>
</form>
`
}
export default {
components: {
ModalForm
},
data () {
return {
isComponentModalActive: false,
resetEmail: '',
feedback: null
}
},
methods: {
sendResetMail () {
var auth = firebase.auth()
var emailAddress = this.resetEmail
auth.sendPasswordResetEmail(emailAddress).then(function () {
// Email sent.
console.log('email send')
}).catch(function (error) {
// An error happened.
this.feedback = error.message
console.log(error.message)
})
}
}
}
</script>
This is the file where I use the ForgotPassword.vue component,
Login.vue:
<template>
<section class="section">
<div class="container">
<div class="columns">
<div class="column"></div>
<div class="column is-half">
<div class="box">
<h1 class="title">Login</h1>
<form #submit.prevent="login">
<b-field label="Email" :message="feedback" :type="type">
<b-input placeholder="Email" icon="email" type="email" v-model="email">
</b-input>
</b-field>
<b-field label="Password" :message="feedback" :type="type">
<b-input placeholder="Password" type="password" icon="textbox-password" password-reveal v-model="password">
</b-input>
</b-field>
<button type="submit" class="button is-primary is-fullwidth">Login</button>
<div class="field">
<div class="control">
<p class="control has-text-centered">
<ForgotPassword/>
</p>
</div>
</div>
<div class="field">
<div class="control">
<p class="control has-text-centered">
Don't have an account?
<a href="/register">
<router-link :to="{ name: 'Signup' }">
Signup
</router-link>
</a>
</p>
</div>
</div>
</form>
</div>
</div>
<div class="column"></div>
</div>
</div>
</section>
</template>
<script>
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'
import ForgotPassword from '#/components/auth/ForgotPassword'
export default {
name: 'login',
metaInfo: {
// specify a metaInfo.title, this title will be used
title: 'Login',
// all titles will be injected into this template
titleTemplate: '%s | Wterdrops.com'
},
components: {
ForgotPassword
},
data () {
return {
email: null,
password: null,
feedback: null,
type: null
}
},
methods: {
login () {
if (this.email && this.password) {
firebase
.auth()
.signInWithEmailAndPassword(this.email, this.password)
.then(cred => {
this.$router.push({
name: 'Dashboard'
})
})
.catch(err => {
this.feedback = err.message
})
this.feedback = null
} else {
this.feedback = 'Please fill in both fields.'
this.type = 'is-danger'
}
}
}
}
</script>
<style>
.login {
max-width: 400px;
margin-top: 60px;
}
</style>
I would be very grateful if someone can explain to me what I'm missing :)
You are referencing
<form #submit.prevent="sendResetMail">
inside your ModalForm component.
The problem is that this template is going to look for the method sendResetMail on that ModalForm component template when it gets rendered since you the code is referencing it. However this sendResetMail method is not directly associated on that.
You can consider using a mix-in if you need to use the sendResetMail in many places , or maybe just move that method directly to the same component "ModalForm" that is referencing it.
You can also look into for example eventBus to trigger the method by emitting an event.
The simplest option if you only need to call it from the MOdalForm component is to just move the sendResetMail direcly to that component. I believe that would probably fix your issue.