I'm tyring to create a autocomplete input with deboand for some reason I am not able to get it to work properly. I am able to select a suggestion from the drop down and show it briefly on the screen, but then it resets alongside the input itself.
I'm pretty sure the error comes from the selectSuggestion
function's last line to clear the valueInner
, but clearing the input once the value is selected is what I want. I don't want, however, the selected value to clear after that has happened.
It could be a combination of the selectSuggestion
function and the watch
on valueInner
, but I also need that watch
to make the debounce work.
Any ideas on how I can fix this?
CodeSandbox Link: https://codesandbox.io/s/stoic-forest-7w19oj?file=/src/components/Autocomplete.vue
App.vue
<template>
<Autocomplete v-model="text" :suggestions="['something','hairstyle','cheese']"/>
<p>Selected Suggestion:{{text}}</p>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import Autocomplete from '../src/components/Autocomplete'
export default defineComponent({
name: "App",
setup(){
const text = ref('')
return { text }
},
components: {
Autocomplete,
Autocomplete2,
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Autocomplete
<template>
<input
ref="inputRef"
v-model="valueInner"
:disabled="disabled || undefined"
:placeholder="placeholder"
:readonly="readonly || undefined"
:required="required || undefined"
:autofocus="autofocus"
:autocomplete="autocomplete"
:suggestions="suggestions"
/>
<ul v-if="filterSuggestions.length">
<li>
Showing {{ filterSuggestions.length }} of {{ suggestions.length }} results
</li>
<li
v-for="suggestion in filterSuggestions"
:key="suggestion"
@click="selectSuggestion(suggestion)"
>
{{ suggestion}}
</li>
</ul>
</template>
<script lang="ts">
import {
defineComponent,
ref,
onMounted,
nextTick,
watch,
PropType,
computed,
} from 'vue'
export default defineComponent({
name: 'Autocomplete',
inheritAttrs: false,
props: {
label: {
type: String,
},
disabled: { type: Boolean, default: false },
placeholder: { type: String },
readonly: { type: Boolean },
required: { type: Boolean },
modelValue: { type: [String, Number], default: '' },
autocomplete: { type: Boolean, default: false },
suggestions: {
type: Array as PropType<string[]>,
default: ['Hamburger', 'Fries', 'Peanuts'],
},
debounce: { type: Number, default: undefined },
},
emits: ['update:modelValue'],
setup(props, { emit }) {
let debounceInner = 0
if (typeof props.debounce === 'number') {
debounceInner = props.debounce
} debounceInner = 1000
const inputRef = ref<HTMLElement | null>(null)
const isFocused = ref(false)
watch(isFocused, (oldVal, newVal) => {
console.log(`isFocused → `, isFocused)
})
let timeout: any = null
const valueInner = ref<any>(props.modelValue)
console.log(valueInner.value)
watch(valueInner, (newVal, oldVal) => {
const debounceMs = debounceInner
if (debounceMs > 0) {
clearTimeout(timeout)
timeout = setTimeout(() => emitInput(newVal), debounceMs)
} else {
emitInput(newVal)
}
})
function emitInput(newVal: any) {
let payload = newVal
emit('update:modelValue', payload)
}
let selectedSuggestion = ref('')
const suggestions = ['United States', 'Japan', 'Italy', 'Australia', 'Germany', 'Poland', 'Ukraine']
const filterSuggestions = computed(() => {
if (valueInner.value === '') {
return []
}
let matches = 0
return suggestions.filter((suggestion) =>{
if (
suggestion.toLowerCase().includes(valueInner.value.toLowerCase())
&& matches < 10
) {
matches
return suggestion
}
})
})
const selectSuggestion = (suggestion) => {
selectedSuggestion.value = suggestion
emitInput(suggestion)
valueInner.value = ''
}
function emitInput(newVal: any) {
let payload = newVal
emit('update:modelValue', payload)
}
watch(valueInner, (newVal, oldVal) => {
const debounceMs = debounceInner
if (debounceMs > 0) {
clearTimeout(timeout)
timeout = setTimeout(() => emitInput(newVal), debounceMs)
} else {
emitInput(newVal)
}
})
return {
inputRef,
valueInner,
suggestions,
filterSuggestions,
selectSuggestion,
selectedSuggestion,
}
},
})
</script>
<style lang="sass" scoped>
li
list-style: none
</style>
CodePudding user response:
The error is indeed from clearing valueInner.value
, which triggers the watch on it, which emits the blank value as the update:modelValue
event data, which overwrites the model value.
A quick fix is to use null
instead of an empty string (to distinguish between an internal clear versus user input) when resetting valueInner.value
:
const selectSuggestion = (suggestion) => {
selectedSuggestion.value = suggestion
emitInput(suggestion)
valueInner.value = null