Vue v-model data is from ajax undefined value - vue.js

I used the vue 2. I had a data from ajax, this is my code example:
<template>
<div>
<input type="input" class="form-control" v-model="siteInfo.siteId">
<input type="input" class="form-control" v-model="siteInfo.info.name">
<input type="input" class="form-control" v-model="siteInfo.accountData.name">
</div>
</template>
<script>
export default {
name: 'Site',
data() {
return {
siteInfo: {},
/* siteInfoName: '', */
}
},
/*computed: {
siteInfoName: function() {
return siteInfo.info.name || '';
},
...
},*/
methods: {
getData() {
// do ajax get data
this.$http.post('URL', {POSTDATA}).then(response => {
/*
response example
{ body:
data: {
sitdeId: 1,
info: { name: 'test'},
accountData: { name: 'accountTest'},
}
}
*/
this.siteInfo = response.body.data;
})
}
},
mounted() {
this.getData();
}
}
</script>
I got a warring message
[Vue warn]: Error in render: "TypeError: Cannot read property 'name'
of undefined"
I can use computed to fix it, but if I had a lot model, I should
write a lot computed.
I should create a lot data for those model?
I should not use an object to bind a lot model?
Does it have another solution for this situation? Thanks your help.

Before the data loads siteInfo.info will be undefined, so you can't access name in the v-model:
v-model="siteInfo.info.name"
Likewise for siteInfo.accountData.name.
My suggestion would be to set the initial value of siteInfo to null and then put a v-if="siteInfo" on the main div. Alternatively you could put a v-if on the individual input elements that checks for siteInfo.info and siteInfo.accountData.
You may also want to consider showing alternative content, such as a load mask, while the data is loading.

Don't be worried about too many v-models - you can do an iteration on the Object - like with Object.entries().
Vue.component('list-input-element', {
props: ['siteLabel', 'siteInfo'],
template: '<div><label>{{siteLabel}}<input type="input" class="form-control" v-model="siteInfo"></label></div>'
})
new Vue({
name: 'Site',
el: '#app',
data() {
return {
siteInfo: {},
}
},
methods: {
getData() {
// using mockup data for this example
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => {
console.log(json)
this.siteInfo = json
})
// do ajax get data
/*this.$http.post('URL', {
POSTDATA
}).then(response => {
this.siteInfo = response.body.data;
})*/
}
},
mounted() {
this.getData();
}
})
div {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<list-input-element v-for="siteInfo in Object.entries(siteInfo)" :site-label="siteInfo[0]" :site-info="siteInfo[1]" />
</div>
Rounding up
So, when you do the single file template, use a computed value, and return an Object from that.
Base your v-for on that computed, and you'll have no problems.
Something like this:
<template>
<div>
<input type="input" class="form-control" v-for="infoEl in siteInfoComputed" v-model="infoEl">
</div>
</template>
<script>
export default {
name: 'Site',
data() {
return {
siteInfo: {},
}
},
computed: {
siteInfoComputed: function() {
// you could check for all the keys-values you want here, and handle
// 'undefined' problem here
// so, actually you "create" the Object here that you're going to use
let ret = {}
// checking if this.siteInfo exists
if (Object.keys(this.siteInfo).length) ret = this.siteInfo
return ret
},
},
methods: {
getData() {
// do ajax get data
this.$http.post('URL', {POSTDATA}).then(response => {
/*
response example
{ body:
data: {
sitdeId: 1,
info: { name: 'test'},
accountData: { name: 'accountTest'},
}
}
*/
this.siteInfo = response.body.data;
})
}
},
mounted() {
this.getData();
}
}
</script>

Related

VueJS input with v-model breaks the user input [duplicate]

I have a simple input box in a Vue template and I would like to use debounce more or less like this:
<input type="text" v-model="filterKey" debounce="500">
However the debounce property has been deprecated in Vue 2. The recommendation only says: "use v-on:input + 3rd party debounce function".
How do you correctly implement it?
I've tried to implement it using lodash, v-on:input and v-model, but I am wondering if it is possible to do without the extra variable.
In template:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
In script:
data: function () {
return {
searchInput: '',
filterKey: ''
}
},
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
The filterkey is then used later in computed props.
I am using debounce NPM package and implemented like this:
<input #input="debounceInput">
methods: {
debounceInput: debounce(function (e) {
this.$store.dispatch('updateInput', e.target.value)
}, config.debouncers.default)
}
Using lodash and the example in the question, the implementation looks like this:
<input v-on:input="debounceInput">
methods: {
debounceInput: _.debounce(function (e) {
this.filterKey = e.target.value;
}, 500)
}
Option 1: Re-usable, no deps
- Recommended if needed more than once in your project
/helpers.js
export function debounce (fn, delay) {
var timeoutID = null
return function () {
clearTimeout(timeoutID)
var args = arguments
var that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
Typescript?
export function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
let timeoutID: number | null = null;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
fn.apply(this, args);
}, delay);
} as T;
}
Or if using a d.ts:
declare function debounce(fn: (...args: any[]) => void, delay: number): (...args: any[]) => void;
/Component.vue
<script>
import {debounce} from './helpers'
export default {
data () {
return {
input: '',
debouncedInput: ''
}
},
watch: {
input: debounce(function (newVal) {
this.debouncedInput = newVal
}, 500)
}
}
</script>
Codepen
Option 2: In-component, also no deps
- Recommended if using once or in small project
/Component.vue
<template>
<input type="text" v-model="input" />
</template>
<script>
export default {
data: {
timeout: null,
debouncedInput: ''
},
computed: {
input: {
get() {
return this.debouncedInput
},
set(val) {
if (this.timeout) clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.debouncedInput = val
}, 300)
}
}
}
}
</script>
Codepen
Assigning debounce in methods can be trouble. So instead of this:
// Bad
methods: {
foo: _.debounce(function(){}, 1000)
}
You may try:
// Good
created () {
this.foo = _.debounce(function(){}, 1000);
}
It becomes an issue if you have multiple instances of a component - similar to the way data should be a function that returns an object. Each instance needs its own debounce function if they are supposed to act independently.
Here's an example of the problem:
Vue.component('counter', {
template: '<div>{{ i }}</div>',
data: function(){
return { i: 0 };
},
methods: {
// DON'T DO THIS
increment: _.debounce(function(){
this.i += 1;
}, 1000)
}
});
new Vue({
el: '#app',
mounted () {
this.$refs.counter1.increment();
this.$refs.counter2.increment();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<div>Both should change from 0 to 1:</div>
<counter ref="counter1"></counter>
<counter ref="counter2"></counter>
</div>
Very simple without lodash
handleScroll: function() {
if (this.timeout)
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
// your action
}, 200); // delay
}
I had the same problem and here is a solution that works without plugins.
Since <input v-model="xxxx"> is exactly the same as
<input
v-bind:value="xxxx"
v-on:input="xxxx = $event.target.value"
>
(source)
I figured I could set a debounce function on the assigning of xxxx in xxxx = $event.target.value
like this
<input
v-bind:value="xxxx"
v-on:input="debounceSearch($event.target.value)"
>
methods:
debounceSearch(val){
if(search_timeout) clearTimeout(search_timeout);
var that=this;
search_timeout = setTimeout(function() {
that.xxxx = val;
}, 400);
},
If you need a very minimalistic approach to this, I made one (originally forked from vuejs-tips to also support IE) which is available here: https://www.npmjs.com/package/v-debounce
Usage:
<input v-model.lazy="term" v-debounce="delay" placeholder="Search for something" />
Then in your component:
<script>
export default {
name: 'example',
data () {
return {
delay: 1000,
term: '',
}
},
watch: {
term () {
// Do something with search term after it debounced
console.log(`Search term changed to ${this.term}`)
}
},
directives: {
debounce
}
}
</script>
Please note that I posted this answer before the accepted answer. It's not
correct. It's just a step forward from the solution in the
question. I have edited the accepted question to show both the author's implementation and the final implementation I had used.
Based on comments and the linked migration document, I've made a few changes to the code:
In template:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
In script:
watch: {
searchInput: function () {
this.debounceInput();
}
},
And the method that sets the filter key stays the same:
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
This looks like there is one less call (just the v-model, and not the v-on:input).
In case you need to apply a dynamic delay with the lodash's debounce function:
props: {
delay: String
},
data: () => ({
search: null
}),
created () {
this.valueChanged = debounce(function (event) {
// Here you have access to `this`
this.makeAPIrequest(event.target.value)
}.bind(this), this.delay)
},
methods: {
makeAPIrequest (newVal) {
// ...
}
}
And the template:
<template>
//...
<input type="text" v-model="search" #input="valueChanged" />
//...
</template>
NOTE: in the example above I made an example of search input which can call the API with a custom delay which is provided in props
Although pretty much all answers here are already correct, if anyone is in search of a quick solution I have a directive for this.
https://www.npmjs.com/package/vue-lazy-input
It applies to #input and v-model, supports custom components and DOM elements, debounce and throttle.
Vue.use(VueLazyInput)
new Vue({
el: '#app',
data() {
return {
val: 42
}
},
methods:{
onLazyInput(e){
console.log(e.target.value)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash/lodash.min.js"></script><!-- dependency -->
<script src="https://unpkg.com/vue-lazy-input#latest"></script>
<div id="app">
<input type="range" v-model="val" #input="onLazyInput" v-lazy-input /> {{val}}
</div>
To create debounced methods you can use computeds, that way they won't be shared across multiple instances of your component:
<template>
<input #input="handleInputDebounced">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
timeout: {
type: Number,
default: 200,
},
},
methods: {
handleInput(event) {
// input handling logic
},
},
computed: {
handleInputDebounced() {
return debounce(this.handleInput, this.timeout);
},
},
}
</script>
You can make it work with uncontrolled v-model as well:
<template>
<input v-model="debouncedModel">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
value: String,
timeout: {
type: Number,
default: 200,
},
},
methods: {
updateValue(value) {
this.$emit('input', value);
},
},
computed: {
updateValueDebounced() {
return debounce(this.updateValue, this.timeout);
},
debouncedModel: {
get() { return this.value; },
set(value) { this.updateValueDebounced(value); }
},
},
}
</script>
Here is a vue3 way
...
<input v-model="searchInput">
...
setup(){
const searchInput = ref(null)
const timeoutID = ref(null)
watch(searchInput, (new, old) => {
clearTimeout(timeoutID.value)
timeoutID.value = setTimeout(() => {
//Call function for searching
}, 500) //millisecons before it is run
})
return {...}
}
If you are using Vue you can also use v.model.lazy instead of debounce but remember v.model.lazy will not always work as Vue limits it for custom components.
For custom components you should use :value along with #change.native
<b-input :value="data" #change.native="data = $event.target.value" ></b-input>
1 Short version using arrow function, with default delay value
file: debounce.js in ex: ( import debounce from '../../utils/debounce' )
export default function (callback, delay=300) {
let timeout = null
return (...args) => {
clearTimeout(timeout)
const context = this
timeout = setTimeout(() => callback.apply(context, args), delay)
}
}
2 Mixin option
file: debounceMixin.js
export default {
methods: {
debounce(func, delay=300) {
let debounceTimer;
return function() {
// console.log("debouncing call..");
const context = this;
const args = arguments;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(context, args), delay);
// console.log("..done");
};
}
}
};
Use in vueComponent:
<script>
import debounceMixin from "../mixins/debounceMixin";
export default {
mixins: [debounceMixin],
data() {
return {
isUserIdValid: false,
};
},
mounted() {
this.isUserIdValid = this.debounce(this.checkUserIdValid, 1000);
},
methods: {
isUserIdValid(id){
// logic
}
}
</script>
another option, example
Vue search input debounce
Here's an example Vue 2 component that demonstrates how to use debounce.
<template>
<div>
<v-btn #click="properDebounceMyMethod">Proper debounce</v-btn>
<v-btn #click="notWorkingDebounceMyMethod">!debounce</v-btn>
<v-btn #click="myMethod">normal call</v-btn>
</div>
</template>
<script lang="ts" >
import { defineComponent } from '#vue/composition-api';
import { debounce } from 'lodash';
export default defineComponent({
name: 'DebounceExample',
created() {
// debounce instance method dynamically on created hook
this.properDebounceMyMethod = debounce(this.properDebounceMyMethod, 500);
},
methods: {
properDebounceMyMethod(){
this.myMethod();
},
notWorkingDebounceMyMethod() {
debounce(this.myMethod, 500);
},
myMethod() {
console.log('hi from my method');
},
}
});
</script>
If you could move the execution of the debounce function into some class method you could use a decorator from the utils-decorators lib (npm install --save utils-decorators):
import {debounce} from 'utils-decorators';
class SomeService {
#debounce(500)
getData(params) {
}
}
I was able to use debounce with very little implementation.
I am using Vue 2.6.14 with boostrap-vue:
Add this pkg to your package.json: https://www.npmjs.com/package/debounce
Add this to main.js:
import { debounce } from "debounce";
Vue.use(debounce);
In my component I have this input:
<b-form-input
debounce="600"
#update="search()"
trim
id="username"
v-model="form.userName"
type="text"
placeholder="Enter username"
required
>
</b-form-input>
All it does is call the search() method and the search method uses the form.userName for perform the search.
<template>
<input type="text" v-model="search" #input="debouncedSearch" />
</template>
<script>
import _ from 'lodash';
export default {
data() {
return {
search: '',
};
},
methods: {
search() {
// Perform the search here
console.log(this.search);
},
},
created() {
this.debouncedSearch = _.debounce(this.search, 1000);
},
};
</script>
public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)
vue-property-decorator

Vue. Autosave input value with localstorage

I have a component with a form for which I need to implement data autosave when entering content. How to apply an example for v-model = "titleName" to v-model = "article.title[currentLanguage]"?
P.S. currentLanguage is taken from store
<div id="app">
Title is <input type="text" name="titleName" id="titleName" v-model="titleName">
Title is <input type="text" name="article" id="article"
v-model="article.title.en">
</div>
<script>
var app = new Vue({
el:'#app',
data: {
titleName: '',
article: {
title: {
en: null,
ru: null
}
}
},
computed: {
availableLanguages() {
return this.$store.state.languages;
}
},
created() {
this.setDefaults();
},
mounted () {
const SAVED = localStorage.getItem('content')
if (SAVED) {
this.titleName = SAVED
}
},
watch: {
titleName(newTitleName) {
localStorage.content = newTitleName;
}
}
</script>
https://codepen.io/pershay/pen/EJQGGO
Just use localStorage.setItem('titleName', newTitleName) and localStorage.getItem('titleName')
You don't need to load on mounted. Place it in the data function itself. I think this is the better way:
var app = new Vue({
data:() => ({
titleName: localStorage.getItem('titleName'),
article: JSON.parse(localStorage.getItem('article'))
}),
watch: {
titleName(newTitleName) {
localStorage.setItem('titleName', newTitleName)
},
article(newArticle) {
handler: function(newArticle) {
localStorage.setItem('article', JSON.stringify(newArticle))
},
deep: true
},
}
The example above also includes a deep object watch.

vue computed cannot get the the attributes in the object

Account to the defind of vuex
// inside mutations
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
// html
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
and I code this
<input type="text" v-model="data.reference">
data () {
return {
data:{
...
reference: '',
}
}
},
computed: {
'data.reference':{
get () {
return this.$store.state.currentKbdata.reference
},
set (value) {
console.log(222)
this.$store.commit('updateReference', value)
}
}
}
And when i enter the input the 222 is not show up
.........................................................................
You cannot define computed getters with dot notation like you can watchers. Here's a fiddle of that not working, where you can see the error in the console reads:
Cannot read property 'reference' of undefined.
Also, it appears that you are attempting to define a computed property that already exists as a property defined in the data method. In this fiddle, you can see that that also won't work. The value defined in the data method takes precedence over the computed property definition.
Anyways, from your example, I don't see why you need to create a computed property on a nested object property at all.
Just use a normal definition for a computed property:
const store = new Vuex.Store({
state: {
reference: '',
},
mutations: {
updateReference(state, reference) {
state.reference = reference;
}
}
});
new Vue({
el: '#app',
store,
computed: {
reference: {
get() {
return this.$store.state.reference;
},
set(val) {
this.$store.commit('updateReference', val);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.min.js"></script>
<div id="app">
<input type="text" v-model="reference">
{{ $store.state.reference }}
</div>
If you really need to set the value of data.reference, then there are many ways you could make that happen. One would be to turn it into a computed property as well:
const store = new Vuex.Store({
state: {
reference: '',
},
mutations: {
updateReference(state, reference) {
state.reference = reference;
}
}
});
new Vue({
el: '#app',
store,
computed: {
reference: {
get() {
return this.$store.state.reference;
},
set(val) {
this.$store.commit('updateReference', val);
}
},
data() {
return { reference: this.reference };
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.min.js"></script>
<div id="app">
<input type="text" v-model="reference">
<div>data.reference: {{ data.reference }}</div>
<div>$store.state.reference: {{ $store.state.reference }}</div>
</div>

VueJs Nested props coming through undefined

I am trying to access an array which is part of a prop (event) passed into a component, but when in created() or mounted() the array part of the event prop (the rest is fine) comes through as undefined.
As can be seen below, when I inspect the props in the vue chrome plugin, the registration_fields are there.
I can add a watcher to the event prop and can access the registration_fields that way, but this seems very awkward to have to do this to access already passed in data.
This is from the Chrome vue inspector:
event:Object
address1_field:"Some Address 1"
address2_field:"Some Address 2"
approved:true
registration_fields:Array[1]
This is what part of my vue file looks like:
export default {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
},
watch: {
event() {
this.regFields = this.event.registration_fields //Can access it here
});
}
}
}
I am using Vue 2.4.4
This is how the component is called:
<template>
<tickets v-if="event" :event="event"></tickets>
</template>
<script>
import tickets from './main_booking/tickets.vue'
export default {
created() {
var self = this;
this.$http.get('events/123').then(response => {
self.event = response.data
}).catch(e => {
alert('Error here!');
})
},
data: function () {
return {event: {}}
},
components: {
tickets: tickets
}
}
</script>
Thank you
It actually works fine without the watcher.
new Vue({
el: '#app',
data: {
event: undefined
},
components: {
subC: {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
}
}
},
mounted() {
setTimeout(() => {
this.event = {
registration_fields: [1, 3]
};
}, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c v-if="event" :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>
If, as Belmin Bedak suggests in the comment below, event is populated asynchronously, it comes in as undefined because it's undefined. In that case, you need a watcher, or, somewhat more elegantly, use a computed:
new Vue({
el: '#app',
data: {
event: {}
},
components: {
subC: {
props: ['event'],
computed: {
regFields() {
return this.event.registration_fields;
}
}
}
},
// delay proper population
mounted() {
setTimeout(() => { this.event = {registration_fields: [1,2,3]}; }, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>

How to implement debounce in Vue2?

I have a simple input box in a Vue template and I would like to use debounce more or less like this:
<input type="text" v-model="filterKey" debounce="500">
However the debounce property has been deprecated in Vue 2. The recommendation only says: "use v-on:input + 3rd party debounce function".
How do you correctly implement it?
I've tried to implement it using lodash, v-on:input and v-model, but I am wondering if it is possible to do without the extra variable.
In template:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
In script:
data: function () {
return {
searchInput: '',
filterKey: ''
}
},
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
The filterkey is then used later in computed props.
I am using debounce NPM package and implemented like this:
<input #input="debounceInput">
methods: {
debounceInput: debounce(function (e) {
this.$store.dispatch('updateInput', e.target.value)
}, config.debouncers.default)
}
Using lodash and the example in the question, the implementation looks like this:
<input v-on:input="debounceInput">
methods: {
debounceInput: _.debounce(function (e) {
this.filterKey = e.target.value;
}, 500)
}
Option 1: Re-usable, no deps
- Recommended if needed more than once in your project
/helpers.js
export function debounce (fn, delay) {
var timeoutID = null
return function () {
clearTimeout(timeoutID)
var args = arguments
var that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
Typescript?
export function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
let timeoutID: number | null = null;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
fn.apply(this, args);
}, delay);
} as T;
}
Or if using a d.ts:
declare function debounce(fn: (...args: any[]) => void, delay: number): (...args: any[]) => void;
/Component.vue
<script>
import {debounce} from './helpers'
export default {
data () {
return {
input: '',
debouncedInput: ''
}
},
watch: {
input: debounce(function (newVal) {
this.debouncedInput = newVal
}, 500)
}
}
</script>
Codepen
Option 2: In-component, also no deps
- Recommended if using once or in small project
/Component.vue
<template>
<input type="text" v-model="input" />
</template>
<script>
export default {
data: {
timeout: null,
debouncedInput: ''
},
computed: {
input: {
get() {
return this.debouncedInput
},
set(val) {
if (this.timeout) clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.debouncedInput = val
}, 300)
}
}
}
}
</script>
Codepen
Assigning debounce in methods can be trouble. So instead of this:
// Bad
methods: {
foo: _.debounce(function(){}, 1000)
}
You may try:
// Good
created () {
this.foo = _.debounce(function(){}, 1000);
}
It becomes an issue if you have multiple instances of a component - similar to the way data should be a function that returns an object. Each instance needs its own debounce function if they are supposed to act independently.
Here's an example of the problem:
Vue.component('counter', {
template: '<div>{{ i }}</div>',
data: function(){
return { i: 0 };
},
methods: {
// DON'T DO THIS
increment: _.debounce(function(){
this.i += 1;
}, 1000)
}
});
new Vue({
el: '#app',
mounted () {
this.$refs.counter1.increment();
this.$refs.counter2.increment();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<div>Both should change from 0 to 1:</div>
<counter ref="counter1"></counter>
<counter ref="counter2"></counter>
</div>
Very simple without lodash
handleScroll: function() {
if (this.timeout)
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
// your action
}, 200); // delay
}
I had the same problem and here is a solution that works without plugins.
Since <input v-model="xxxx"> is exactly the same as
<input
v-bind:value="xxxx"
v-on:input="xxxx = $event.target.value"
>
(source)
I figured I could set a debounce function on the assigning of xxxx in xxxx = $event.target.value
like this
<input
v-bind:value="xxxx"
v-on:input="debounceSearch($event.target.value)"
>
methods:
debounceSearch(val){
if(search_timeout) clearTimeout(search_timeout);
var that=this;
search_timeout = setTimeout(function() {
that.xxxx = val;
}, 400);
},
If you need a very minimalistic approach to this, I made one (originally forked from vuejs-tips to also support IE) which is available here: https://www.npmjs.com/package/v-debounce
Usage:
<input v-model.lazy="term" v-debounce="delay" placeholder="Search for something" />
Then in your component:
<script>
export default {
name: 'example',
data () {
return {
delay: 1000,
term: '',
}
},
watch: {
term () {
// Do something with search term after it debounced
console.log(`Search term changed to ${this.term}`)
}
},
directives: {
debounce
}
}
</script>
Please note that I posted this answer before the accepted answer. It's not
correct. It's just a step forward from the solution in the
question. I have edited the accepted question to show both the author's implementation and the final implementation I had used.
Based on comments and the linked migration document, I've made a few changes to the code:
In template:
<input type="text" v-on:input="debounceInput" v-model="searchInput">
In script:
watch: {
searchInput: function () {
this.debounceInput();
}
},
And the method that sets the filter key stays the same:
methods: {
debounceInput: _.debounce(function () {
this.filterKey = this.searchInput;
}, 500)
}
This looks like there is one less call (just the v-model, and not the v-on:input).
In case you need to apply a dynamic delay with the lodash's debounce function:
props: {
delay: String
},
data: () => ({
search: null
}),
created () {
this.valueChanged = debounce(function (event) {
// Here you have access to `this`
this.makeAPIrequest(event.target.value)
}.bind(this), this.delay)
},
methods: {
makeAPIrequest (newVal) {
// ...
}
}
And the template:
<template>
//...
<input type="text" v-model="search" #input="valueChanged" />
//...
</template>
NOTE: in the example above I made an example of search input which can call the API with a custom delay which is provided in props
Although pretty much all answers here are already correct, if anyone is in search of a quick solution I have a directive for this.
https://www.npmjs.com/package/vue-lazy-input
It applies to #input and v-model, supports custom components and DOM elements, debounce and throttle.
Vue.use(VueLazyInput)
new Vue({
el: '#app',
data() {
return {
val: 42
}
},
methods:{
onLazyInput(e){
console.log(e.target.value)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash/lodash.min.js"></script><!-- dependency -->
<script src="https://unpkg.com/vue-lazy-input#latest"></script>
<div id="app">
<input type="range" v-model="val" #input="onLazyInput" v-lazy-input /> {{val}}
</div>
To create debounced methods you can use computeds, that way they won't be shared across multiple instances of your component:
<template>
<input #input="handleInputDebounced">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
timeout: {
type: Number,
default: 200,
},
},
methods: {
handleInput(event) {
// input handling logic
},
},
computed: {
handleInputDebounced() {
return debounce(this.handleInput, this.timeout);
},
},
}
</script>
You can make it work with uncontrolled v-model as well:
<template>
<input v-model="debouncedModel">
<template>
<script>
import debounce from 'lodash.debouce';
export default {
props: {
value: String,
timeout: {
type: Number,
default: 200,
},
},
methods: {
updateValue(value) {
this.$emit('input', value);
},
},
computed: {
updateValueDebounced() {
return debounce(this.updateValue, this.timeout);
},
debouncedModel: {
get() { return this.value; },
set(value) { this.updateValueDebounced(value); }
},
},
}
</script>
Here is a vue3 way
...
<input v-model="searchInput">
...
setup(){
const searchInput = ref(null)
const timeoutID = ref(null)
watch(searchInput, (new, old) => {
clearTimeout(timeoutID.value)
timeoutID.value = setTimeout(() => {
//Call function for searching
}, 500) //millisecons before it is run
})
return {...}
}
If you are using Vue you can also use v.model.lazy instead of debounce but remember v.model.lazy will not always work as Vue limits it for custom components.
For custom components you should use :value along with #change.native
<b-input :value="data" #change.native="data = $event.target.value" ></b-input>
1 Short version using arrow function, with default delay value
file: debounce.js in ex: ( import debounce from '../../utils/debounce' )
export default function (callback, delay=300) {
let timeout = null
return (...args) => {
clearTimeout(timeout)
const context = this
timeout = setTimeout(() => callback.apply(context, args), delay)
}
}
2 Mixin option
file: debounceMixin.js
export default {
methods: {
debounce(func, delay=300) {
let debounceTimer;
return function() {
// console.log("debouncing call..");
const context = this;
const args = arguments;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(context, args), delay);
// console.log("..done");
};
}
}
};
Use in vueComponent:
<script>
import debounceMixin from "../mixins/debounceMixin";
export default {
mixins: [debounceMixin],
data() {
return {
isUserIdValid: false,
};
},
mounted() {
this.isUserIdValid = this.debounce(this.checkUserIdValid, 1000);
},
methods: {
isUserIdValid(id){
// logic
}
}
</script>
another option, example
Vue search input debounce
Here's an example Vue 2 component that demonstrates how to use debounce.
<template>
<div>
<v-btn #click="properDebounceMyMethod">Proper debounce</v-btn>
<v-btn #click="notWorkingDebounceMyMethod">!debounce</v-btn>
<v-btn #click="myMethod">normal call</v-btn>
</div>
</template>
<script lang="ts" >
import { defineComponent } from '#vue/composition-api';
import { debounce } from 'lodash';
export default defineComponent({
name: 'DebounceExample',
created() {
// debounce instance method dynamically on created hook
this.properDebounceMyMethod = debounce(this.properDebounceMyMethod, 500);
},
methods: {
properDebounceMyMethod(){
this.myMethod();
},
notWorkingDebounceMyMethod() {
debounce(this.myMethod, 500);
},
myMethod() {
console.log('hi from my method');
},
}
});
</script>
If you could move the execution of the debounce function into some class method you could use a decorator from the utils-decorators lib (npm install --save utils-decorators):
import {debounce} from 'utils-decorators';
class SomeService {
#debounce(500)
getData(params) {
}
}
I was able to use debounce with very little implementation.
I am using Vue 2.6.14 with boostrap-vue:
Add this pkg to your package.json: https://www.npmjs.com/package/debounce
Add this to main.js:
import { debounce } from "debounce";
Vue.use(debounce);
In my component I have this input:
<b-form-input
debounce="600"
#update="search()"
trim
id="username"
v-model="form.userName"
type="text"
placeholder="Enter username"
required
>
</b-form-input>
All it does is call the search() method and the search method uses the form.userName for perform the search.
<template>
<input type="text" v-model="search" #input="debouncedSearch" />
</template>
<script>
import _ from 'lodash';
export default {
data() {
return {
search: '',
};
},
methods: {
search() {
// Perform the search here
console.log(this.search);
},
},
created() {
this.debouncedSearch = _.debounce(this.search, 1000);
},
};
</script>
public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)
vue-property-decorator