I learned vue's custom directive today, and start wondering if I can write a custom directive that has same function as v-model. but I find the difficulty to do the two way binding in the directive's hooks, any help?
Yes,
you should pass value props to your component and then emit input for changing value
e.g.:
We have input component:
<template>
<input :value="innerValue" #input="change($event.target.value)">
</template>
<script>
export default {
name: "TextField",
props: ["value"],
computed: {
innerValue() {
return this.value;
}
},
methods: {
change(e) {
console.log(e);
this.$emit("input", e);
}
}
};
</script>
and we use it in parent component:
<template>
<div id="app">
<text-field v-model="value"/>
</div>
</template>
<script>
import TextField from "./components/TextField";
export default {
name: "App",
components: {
TextField
},
data: () => ({
value: ""
})
};
</script>
Related
I'm having trouble getting a route param to pass directly into a component. I followed multiple sets of directions in the docs (including using the Composition API as in the following code), but I'm still getting undefined when the CourseModule.vue first renders.
Route Definition
{
path: '/module/:id',
name: 'Course Module',
props: true,
component: () => import('../views/CourseModule.vue'),
},
CourseModule.vue:
<template>
<div class="AppHome">
<CustomerItem />
<CourseModuleItem :coursemodule-id="this.CoursemoduleId"/>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import CustomerItem from '../components/customers/customer-item.vue';
import CourseModuleItem from '../components/coursemodules/coursemodule-item.vue';
export default {
setup() {
const route = useRoute();
alert(`CourseModule.vue setup: ${route.params.id}`);
return {
CoursemoduleId: route.params.id,
};
},
components: {
CustomerItem,
CourseModuleItem,
},
mounted() {
alert(`CourseModule.vue mounted: ${this.CoursemoduleId}`);
},
};
</script>
coursemodule-item.vue:
<template>
<div id="module">
<div v-if="module.data">
<h2>Course: {{module.data.ModuleName}}</h2>
</div>
<div v-else-if="module.error" class="alert alert-danger">
{{module.error}}
</div>
<Loader v-else-if="module.loading" />
</div>
</template>
<script>
import Loader from '../APILoader.vue';
export default {
props: {
CoursemoduleId: String,
},
components: {
Loader,
},
computed: {
module() {
return this.$store.getters.getModuleById(this.CoursemoduleId);
},
},
mounted() {
alert(`coursemodule-item.vue: ${this.CoursemoduleId}`);
this.$store.dispatch('setModule', this.CoursemoduleId);
},
};
</script>
The output from my alerts are as follows:
CourseModule.vue setup: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
coursemodule-item.vue: undefined
CourseModule.vue mounted: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
As you can see, the path parameter works fine in the top level Vue, but not it's still not getting passed into the component.
your kebab-cased :coursemodule-id props that you're passing to the CourseModuleItem component becomes a camelCased coursemoduleId props
Prop Casing (camelCase vs kebab-case)
try this
// coursemodule-item.vue
...
props: {
coursemoduleId: String,
},
...
mounted() {
alert(`coursemodule-item.vue: ${this.coursemoduleId}`);
this.$store.dispatch('setModule', this.coursemoduleId);
},
Do you know how to change a component dynamically with object prop
App.vue
<template>
<div id="app">
<component :is="current['test'].target.name"> </component>
<input type="button" value="click me" #click="change" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
import Comp from "./components/Comp.vue";
export default {
name: "App",
components: {
HelloWorld,
Comp,
},
data() {
return {
current: {},
};
},
created() {
this.current["test"] = {
index: 0,
target: {
name: "Comp",
},
};
},
methods: {
change() {
const r =
this.current["test"].target.name === "HelloWorld"
? "Comp"
: "HelloWorld";
this.current["test"].target = {
name: r,
};
console.log(this.current["test"]);
},
},
};
</script>
Comp.vue
<template>
<p>Template 2</p>
</template>
HelloWorld.vue
<template>
<p>Template 1</p>
</template>
https://codesandbox.io/s/clever-water-dgbts?file=/src/components/HelloWorld.vue:0-42
The value of the object will change correctly but not the component.
Thank you
The issue here is that the property test is not defined on the object current in the data definition - you're setting the definition in the created() function. This means that Vue does not know to create the reactive getter/setter for that property.
Change your data definition to:
data() {
return {
current: {
test: {
index: 0,
target: {
name: "Comp"
}
}
}
};
}
It is because of the way Vue does its reactivity (requiring pre-defined properties) that I would recommend steering clear of accessing properties as dictionary items i.e. use:
current.test.target.name
instead of
current['test'].target.name
For more information on Vue reactivity see this page: link
I'm new with VueJS, and I'm creating a VueJS app where you can get some informations about a Github User,
(example: https://api.github.com/users/versifiction)
I created a store with VueX, but I need to update the value written by the user in the input,
My "inputValue" is always at "" (its default value) and when I type inside the input, the store value still at ""
I tried this :
Input.vue
<template>
<div class="input">
<input
type="text"
:placeholder="placeholder"
v-model="inputValue"
#change="setInputValue(inputValue)"
#keyup.enter="getResult(inputValue)"
/>
<input type="submit" #click="getResult(inputValue)" />
</div>
</template>
<script>
import store from "../store";
export default {
name: "Input",
props: {
placeholder: String,
},
computed: {
inputValue: () => store.state.inputValue,
},
methods: {
setInputValue: (payload) => {
store.commit("setInputValue", payload);
}
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
and this :
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
inputValue: "",
},
getters: {
getInputValue(state) {
return state.inputValue;
}
},
mutations: {
setInputValue(state, payload) {
console.log("setInputValue");
console.log("payload ", payload);
state.inputValue = payload;
},
},
});
According to the vuex docs in the form handling section you should do :
:value="inputValue"
#change="setInputValue"
and
methods: {
setInputValue: (event) => {
store.commit("setInputValue", event.target.value);
}
}
The simplest and elegant way to bind vuex and a component would be to use computed properties.
The above code would become,
<input
type="text"
:placeholder="placeholder"
v-model="inputValue"
#keyup.enter="getResult(inputValue)"
/>
and inside your computed properties, you'll need to replace inputValue with following code.
computed: {
inputValue: {
set(val){
this.$store.commit(‘mutationName’, val)
},
get() {
return this.$store.stateName
}
}
}
EDIT --- SOLVED
It turns out that isn't really a problem, Vue will auto-bind for you so there's no need to bind manually.
END EDIT ---
I'm trying to pass a method to a callback(or event) to a child component.
Everything works great, except that the function executes in the wrong context.
In react, I would bind the functions in the constructor, I'm not sure what's the solution is in Vue.
Example
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this); // refers to the child component
// can't access this.todos
}
},
components: {
Tasks,
Header
}
}
</script>
Here's the solution to it, turns out you can use the 'created' life cycle hook, this is similar to react when binding in a constructor
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this.todos); // can now access the correct 'this'
}
},
created() {
this.markAsDone = this.markAsDone.bind(this);
},
components: {
Tasks,
Header
}
}
</script>
Sub component code
<template>
<ul>
<li
:class="{isDone:todo.isDone}"
:key="todo.id"
v-for="todo in todos">
<input type='checkbox' #change="markAsDone(todo.id)"/>
{{todo.text}}
</li>
</ul>
</template>
<script>
export default {
name: 'Tasks',
props: ['todos'],
methods: {
markAsDone(id) {
this.$emit('onMarkAsDone', id);
}
}
}
</script>
You can return function in a markAsDone method, like this:
markAsDone() {
return id => {
console.log(this.todos);
}
},
and then when passing method as a prop call it:
:onMarkAsDone="markAsDone()"
Then you can call the prop method inside your Child-Component.
That should give you requested functionality without using bind in created() hook.
In my parent Vue Page I'm calling a FormInput inside my form
new.vue
<b-form #submit.prevent="submit">
<FormInput :name="name"/>
<b-button #click="submit">Save</b-button>
<b-form>
<script>
import FormInput from '~/components/Insiders/FormInput';
export default {
components: {
FormInput
},
data() {
return {
name: 'User A'
}
},
methods: {
submit(event) {
console.log(this.name)
}
}
}
</script>
components/Insiders/FormInput.vue
<b-form-input v-model="name" type="text"></b-form-input>
<script>
export default {
props: {
name: { type: String, required: true }
}
}
</script>
I'm getting an error:
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: "name"
What I'm expecting here is when I change the value in the input from new.vue I should be able to console.log the new value of name when I click the submit button.
How can I solve this?
The right way for this use case is for you to support v-model for your component FormInput.
Essentially, you are building a component for user input. In that case, the component should take input prop and publish its value. Both can be done as a single configuration if you use v-model. ('Input' and 'Output' are configured with a single v-model attribute).
Refer to these articles:
https://alligator.io/vuejs/add-v-model-support/
https://scotch.io/tutorials/add-v-model-support-to-custom-vuejs-component
Edit:
v-model approach makes the FormInput component easy to use. So, the change in New.vue is simple:
<b-form #submit.prevent="submit">
<FormInput :v-model="name"/>
<b-button #click="submit">Save</b-button>
<b-form>
<script>
import FormInput from '~/components/Insiders/FormInput';
export default {
components: {
FormInput
},
data() {
return {
name: 'User A'
}
},
methods: {
submit(event) {
console.log(this.name)
}
}
}
</script>
But, the FormInput component has to still do some extra work, in order not to mutate the actual input.
<b-form-input :value="value" #input='updateVal' type="text"></b-form-input>
<script>
export default {
props: {
value: { type: String, required: true }
},
methods: {
updateVal: function(val){
this.$emit('input', val);
}
}
}
</script>