diff --git a/docs/src/pages/quasar-utils/other-utils.md b/docs/src/pages/quasar-utils/other-utils.md
index 69ef6421d6bb..2f4813f553a8 100644
--- a/docs/src/pages/quasar-utils/other-utils.md
+++ b/docs/src/pages/quasar-utils/other-utils.md
@@ -291,6 +291,33 @@ runSequentialPromises([ /* ... */ ], { threadsNumber: 3 })
})
```
+## Debounced Ref
+Creates a new reference to an existing value in the Vue instance that will be updated in real-time, while the original value will be updated after a debouncing period.
+
+The new value can be used as model for a control that updates frequently (eg. QInput, QSlider, QRange, ...), while the original value will only be updated after debouncing (eg. You have a search field but you only want to perform the remote search API call 300ms after the last change in the search field).
+
+``` js
+import { debouncedRef } from 'quasar'
+
+....
+data () {
+ return {
+ search: '',
+ debouncedSearch: debouncedRef(this, 'search', 500)
+ }
+},
+
+watch: {
+ debouncedSearch (newSearch, oldSearch) {
+ ...
+ }
+}
+```
+
+::: warning
+If the debounced value is an object only modify it as a whole, because it is passed by reference. If you change the value of keys inside the object the change will propagate immediatelly.
+:::
+
## Debounce Function
If your App uses JavaScript to accomplish taxing tasks, a debounce function is essential to ensuring a given task doesn't fire so often that it bricks browser performance. Debouncing a function limits the rate at which the function can fire.
diff --git a/ui/dev/src/pages/form/debounced-ref.vue b/ui/dev/src/pages/form/debounced-ref.vue
new file mode 100644
index 000000000000..d075625800d5
--- /dev/null
+++ b/ui/dev/src/pages/form/debounced-ref.vue
@@ -0,0 +1,72 @@
+
+
+
+
original1: [{{ original1 }}]
+
+
+
debounced1: [{{ debounced1.value }}]
+
+
+
+
+
+
+
original2: [{{ original2 }}]
+
+
+
debounced2test: [{{ debounced2test.value }}]
+
+
+
+
+
+
+
original3: [{{ original3 }}]
+
+
+
+
debounced3: [{{ debounced3.value }}]
+
+
+
+
+
+
diff --git a/ui/src/utils.js b/ui/src/utils.js
index 600ca808fb6f..25f6807adf25 100644
--- a/ui/src/utils.js
+++ b/ui/src/utils.js
@@ -3,6 +3,7 @@ import colors from './utils/colors.js'
import copyToClipboard from './utils/copy-to-clipboard.js'
import date from './utils/date.js'
import debounce from './utils/debounce.js'
+import debouncedRef from './utils/debounced-ref.js'
import dom from './utils/dom.js'
import event, { noop } from './utils/event.js'
import exportFile from './utils/export-file.js'
@@ -24,6 +25,7 @@ export {
copyToClipboard,
date,
debounce,
+ debouncedRef,
dom,
event,
noop,
diff --git a/ui/src/utils/debounced-ref.js b/ui/src/utils/debounced-ref.js
new file mode 100644
index 000000000000..91ecaa543f9a
--- /dev/null
+++ b/ui/src/utils/debounced-ref.js
@@ -0,0 +1,48 @@
+export default function (vm, prop, wait = 250) {
+ const debounced = {
+ get value () {
+ const value = vm[prop]
+
+ if (value !== debounced.propValue) {
+ debounced.propValue = value
+ debounced.internalValue = value
+
+ debounced.timer !== void 0 && clearTimeout(debounced.timer)
+ debounced.timer = void 0
+ }
+
+ return debounced.internalValue
+ },
+
+ set value (value) {
+ if (debounced.internalValue === value) {
+ return
+ }
+
+ debounced.internalValue = value
+
+ debounced.timer !== void 0 && clearTimeout(debounced.timer)
+ debounced.timer = setTimeout(() => {
+ vm[prop] = value
+
+ debounced.timer = void 0
+ }, wait)
+ },
+
+ destroy () {
+ if (debounced.timer !== void 0) {
+ clearTimeout(debounced.timer)
+ debounced.timer = void 0
+
+ vm[prop] = debounced.internalValue
+ }
+ }
+ }
+
+ vm.$once('hook:beforeDestroy', debounced.destroy)
+
+ debounced.propValue = NaN
+ debounced.internalValue = NaN
+
+ return debounced
+}
diff --git a/ui/types/utils.d.ts b/ui/types/utils.d.ts
index 882566436d72..47fbd0fa6798 100644
--- a/ui/types/utils.d.ts
+++ b/ui/types/utils.d.ts
@@ -1,3 +1,5 @@
+import { LooseDictionary } from './ts-helpers'
+
export * from './utils/date';
export * from './utils/colors';
export * from './utils/dom';
@@ -14,6 +16,10 @@ export function debounce any>(
wait?: number,
immediate?: boolean
): F & { cancel(): void };
+export function debouncedRef(vm: T, prop: K, wait?: number): {
+ value: T[K],
+ destroy: () => void
+};
export function exportFile(
fileName: string,
rawData: BlobPart,