I have this component:
<template>
<div>
<label class="col-form-label">
<span>From</span>
</label>
<DatepickerFrom v-on:selected="SetSelectedDateFrom" :value="config.state.fromDate" :disabled="config.disabledFrom"></DatepickerFrom>
</div>
</template>
<script>
import DatepickerFrom from 'vuejs-datepicker'
const dUse = new Date()
export default {
data() {
return {
config: {
disabledFrom: {
to: new Date(dUse.getFullYear() - 1, dUse.getMonth(), dUse.getDate()),
from: new Date(dUse.getFullYear(), dUse.getMonth(), dUse.getDate())
},
state: {
fromDate: new Date(
dUse.getFullYear(),
dUse.getMonth(),
dUse.getDate() - 1
).toString()
}
}
}
},
methods: {
SetSelectedDateFrom: function(selectedDate) {
this.$emit('selected', selectedDate)
}
},
components: {
DatepickerFrom
}
}
</script>
note this part:
:value="config.state.fromDate"
I load this component:
<div class="card selector col-md-4 holder">
<pickerTo v-on:selected="DateSelector2" ></pickerTo>
</div>
import pickerTo from '../components/DateFilterTo'
i have a function that gets the selected value once the a change occurs:
DateSelector2: function(date) {
FromDate = date
}
Once a new date is picked i assign it to a global variable inside of my parent.
Problem:
I have a default date set in the component which I would like to get when I am making a request to the server. I cant figure out how to get this default value since it does not occur within the change event.
I am new to vue.
One of the easiest ways would be to emit the default value when the mounted function fires.
mounted () {
this.$emit('selected', config.state.fromDate)
}
Other than that I would mutate the value into a prop and alter it slightly to allow it to work with v-model so that the default value is passed from the parent using whichever method it chooses to derive that value.
EDIT:
Here's how I would refactor it to use a prop.
<template>
<div>
<label class="col-form-label">
<span>From</span>
</label>
<DatepickerFrom v-on:selected="SetSelectedDateFrom" :value="value" :disabled="config.disabledFrom"></DatepickerFrom>
</div>
</template>
<script>
import DatepickerFrom from 'vuejs-datepicker'
const dUse = new Date()
export default {
props: {
value: {
type: String,
required: true
}
},
data() {
return {
config: {
disabledFrom: {
to: new Date(dUse.getFullYear() - 1, dUse.getMonth(), dUse.getDate()),
from: new Date(dUse.getFullYear(), dUse.getMonth(), dUse.getDate())
},
state: {
}
}
}
},
methods: {
SetSelectedDateFrom: function(selectedDate) {
this.$emit('input', selectedDate)
}
},
components: {
DatepickerFrom
}
}
</script>
now you can use your component with the v-model directive for 2 way binding
<component v-model="dateFrom"></component>
...
data () {
return {
dateFrom: new Date(
dUse.getFullYear(),
dUse.getMonth(),
dUse.getDate() - 1
).toString()
}
}
Related
I have a customized toast component:
<template>
<div v-show="isToastVisible" class="toast-wrapper">
<div class="toast-conatiner">
{{ message }}
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
props: {
message: {
type: String,
default: '',
},
isToastVisible: {
type: Boolean,
default: false,
},
},
};
</script>
I pass state to the property:
<toast :is-toast-visible="isToastVisible" message="please say the word again"/>
I have an action to control the value of isToastVisbible. When I want to toast some message , I set the state to true.
What I want is to make the toast auto closed in 3 seconds, it means that I have to call a setTimeout function when I set isToastVisbible to true.
commit('SET_TOAST_VISIBLILITY', true);
setTimeout(() => {
commit('SET_TOAST_VISIBLILITY', false);
}, 3000);
I try to add watch on isToastVisbible, but it only can works at first time.
<template>
<div v-show="localVisible" class="toast-wrapper">
<div class="toast-conatiner">
{{ message }}
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
props: {
message: {
type: String,
default: '',
},
isToastVisible: {
type: Boolean,
default: false,
},
},
data: () => ({
localVisible: false,
}),
watch: {
isToastVisible(v) {
if (v) {
this.localVisible = true;
setTimeout(() => {
this.localVisible = false;
}, 3000);
}
},
},
};
I am trying to do a better design on the component, but I don't know what is the best practice of this. Hope somebody could give me a hand, thanks a lot.
Try to use toast component with v-model directive. In toast you may use timer to emit hide event:
setTimeout(() => this.$emit('input'), 3000);
On the parent component you may use this one:
<toast-component v-model="isVisible"></toast-component>
Or without v-model sugar:
<toast-component :value="isVisible" #input="isVisible = false"></toast-component>
I have two components and a basic store as per the docs here: https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch.
I want to make it so that when I type into an input the value in a different component is updated by using the store.
Basic example here.
App.vue
<template>
<div id="app">
<h1>Store Demo</h1>
<BaseInputText /> Value From Store: {{ test }}
</div>
</template>
<script>
import BaseInputText from "./components/BaseInputText.vue";
import { store } from "../store.js";
export default {
// This should reactively changed as per the input
computed: {
test: function() {
return store.state.test;
}
},
components: {
BaseInputText
}
};
</script>
BaseInput.vue
<template>
<input type="text" class="input" v-model="test" />
</template>
<script>
import { store } from "../store.js";
export default {
data() {
return {
test: store.state.test
};
},
// When the value changes update the store
watch: {
test: function(newValue) {
store.setTest(newValue);
}
}
};
</script>
store.js
export const store = {
debug: true,
state: {
test: "hi"
},
setTest(newValue) {
if (this.debug) console.log("Set the test field with:", newValue);
this.state.test = newValue;
}
};
I want to make it so that when I type a string into the input the test variable in App.vue is updated. I'm trying to understand how the store pattern works. I'm aware of how to use props.
I also have a working copy here: https://codesandbox.io/s/loz79jnoq?fontsize=14
Updated
2.6.0+
To make store reactive use Vue.observable (added in in 2.6.0+)
store.js
import Vue from 'vue'
export const store = Vue.observable({
debug: true,
state: {
test: 'hi'
}
})
BaseInputText.vue
<input type="text" class="input" v-model="state.test">
...
data() {
return {
state: store.state
};
},
before 2.6.0
store.js
import Vue from 'vue'
export const store = new Vue({
data: {
debug: true,
state: {
test: 'hi'
}
}
})
BaseInputText.vue
<input type="text" class="input" v-model="state.test">
...
data() {
return {
state: store.state
};
}
Old answer
From documentation However, the difference is that computed properties are cached based on their reactive dependencies.
The store is not reactive
Change to
App.vue
data() {
return {
state: store.state
};
},
computed: {
test: function() {
return this.state.test;
}
},
It looks bad but I don't see another way to make it work
Here is a simple Vue 2.0 form component. It consists of a number input and a button, e.g.:
Note that the value of the input is tied to the component's data using v-model. buttonText is passed in as a prop.
What's the best way to pass a default value into the form, so that it initially renders with a value other than 10?
Using props doesn't seem to be the right way to do it because then v-model no longer works properly.
However, data can't be passed in the way props can, as far as I can tell from Vue documentation.
.
<template>
<form v-on:submit.prevent="onSubmit">
<input v-model="amount" type="number" min="1" max="20"></input>
<button type="submit">{{ buttonText }}</button>
</form>
</template>
<script>
export default {
props: [ 'buttonText' ],
data: function() {
return {
amount: 10
}
},
methods: {
onSubmit: function() {
this.$emit("submit", parseInt(this.amount) );
}
}
}
</script>
You can pass in a prop (say initialAmount) and reference that when initializing the amount value in the data function:
export default {
props: {
buttonText: { type: String },
initialAmount: { type: Number, default: 10 },
},
data: function() {
return {
amount: this.initialAmount
}
},
methods: {
onSubmit: function() {
this.$emit("submit", parseInt(this.amount) );
}
}
}
I have a simple application which need to render 2 components dynamically.
Component A - needs to have onClick event.
Component B - needs to have onChange event.
How is it possible to dynamically attach different events to component A/B?
<template>
<component v-bind:is="currentView">
</component>
</template>
<script>
import A from '../components/a.vue'
import B from '../components/b.vue'
export default {
data: function () {
return {
currentView: A
}
},
components: { A, B }
}
</script>
Here is a solution for a little more complicated and realistic use case. In this use case you have to render multiple different components using v-for.
The parent component passes an array of components to create-components. create-components will use v-for on this array, and display all those components with the correct event.
I'm using a custom directive custom-events to achieve this behavior.
parent:
<template>
<div class="parent">
<create-components :components="components"></create-components>
</div>
</template>
<script>
import CreateComponents from '#/components/CreateComponents'
import ComponentA from '#/components/ComponentA'
import ComponentB from '#/components/ComponentB'
export default {
name: 'parent',
data() {
return {
components: [
{
is: ComponentA,
events: {
"change":this.componentA_onChange.bind(this)
}
},
{
is: ComponentB,
events: {
"click":this.componentB_onClick.bind(this)
}
}
]
}
},
methods: {
componentA_onChange() {
alert('componentA_onChange');
},
componentB_onClick() {
alert('componentB_onClick');
}
},
components: { CreateComponents }
};
</script>
create-components:
<template>
<div class="create-components">
<div v-for="(component, componentIndex) in components">
<component v-bind:is="component.is" v-custom-events="component.events"></component>
</div>
</div>
</template>
<script>
export default {
name: 'create-components',
props: {
components: {
type: Array
}
},
directives: {
CustomEvents: {
bind: function (el, binding, vnode) {
let allEvents = binding.value;
if(typeof allEvents !== "undefined"){
let allEventsName = Object.keys(binding.value);
allEventsName.forEach(function(event) {
vnode.componentInstance.$on(event, (eventData) => {
allEvents[event](eventData);
});
});
}
},
unbind: function (el, binding, vnode) {
vnode.componentInstance.$off();
}
}
}
}
</script>
You don't have to dynamically add them.
<component v-bind:is="currentView" #click="onClick" #change="onChange">
If you want to be careful you can bail in the handler of the currentView is not correct.
methods: {
onClick(){
if (this.currentView != A) return
// handle click
},
onChange(){
if (this.currentView != B) return
// handle change
}
}
I have an app in which I have a counter which shows a value, and a button that can increment that value.
I used simple state management from scratch as the docs suggest.
I can add counters to this list with an 'Add Counter' button so that I have multiple counters on the page.
Despite each instance of my counter component having a separate key in the parent component (as per the docs), each instance of the counter shares the same value:
How can I add a separate instance of the same component that has its own state?
Here is the code on webpackbin: http://www.webpackbin.com/41hjaNLXM
Code:
App.vue
<template>
<div id="app">
<counter v-for="n in state.countersAmount" :key="n"></counter>
<button v-on:click="addCounter">Add a Counter</button>
</div>
</template>
<script>
import Counter from './Counter.vue'
const store = {
state: {
countersAmount: 1
},
incrementCounters() {
++this.state.countersAmount
}
}
export default {
data() {
return {
state: store.state
}
},
methods: {
addCounter() {
store.incrementCounters()
}
},
components: {
Counter
}
}
</script>
Counter.vue
<template>
<div>
<h1>{{state.counterValue}}</h1>
<button v-on:click="increment">+</button>
</div>
</template>
<script>
const store = {
state: {
counterValue: 0,
},
increment() {
++this.state.counterValue
}
}
export default {
data() {
return {
state: store.state
}
},
methods: {
increment() {
store.increment()
}
}
}
</script>
You're using the same state for every Counter instance.
const store = {
state: {
counterValue: 0,
},
increment() {
++this.state.counterValue
}
}
The code above will be executed only once, and every instance of this component will share this state.
To change this, just return a new object as the initial state, like so:
<template>
<div>
<h1>{{counterValue}}</h1>
<button v-on:click="increment">+</button>
</div>
</template>
<script>
export default {
data() {
return {
counterValue: 0
}
},
methods: {
increment() {
++this.counterValue;
}
}
}
</script>
The Simple State Management from Scratch you linked, is for shared state between components, as you can see in the picture:
You are always returning the same instance of the component. Instead, you should return a new instance.