Goal
I want to filter a data source used in a for-loop based on the input of a search bar.
Example
I have data source [{ title: "Title1" }, { title: "Title2" }]. I enter "Title1" in my search bar. I only want the filtered data source of the for-loop to show the object with "Title1"
Issue
The searchInputChange(...) method seems to work and the this.filteredPosts data source seems to change correctly. However, the data source does not seem to update in the HTML template. It always contains both objects.
Home.vue
<template>
<Searchfield @searchInputChange="searchInputChange" />
<div v-for="post in filteredPosts" :key="post.id">
{{ post.title }}
</div>
</template>
<script>
import { ref } from 'vue'
import Searchfield from '../components/Searchfield.vue'
export default {
name: 'Home',
components: { Searchfield },
setup() {
let filteredPosts = ref([])
let allPosts = [
{
"id": 1,
"title": "Title1",
},
{
"id": 2,
"title": "Title2",
}
]
filteredPosts.value = [...allPosts]
return { allPosts, filteredPosts }
},
methods: {
searchInputChange(value) {
const filterPosts = this.allPosts.filter(post => post.title.toLowerCase().includes(value.toLowerCase()));
this.filteredPosts.value = [...filterPosts]
}
}
}
</script>
Searchfield.vue
<template>
<input type="text" @change="searchInputChange" placeholder="Search...">
</template>
<script>
export default {
name: 'Searchfield',
setup(props, context) {
const searchInputChange = (event) => {
context.emit('searchInputChange', event.target.value)
}
return { searchInputChange }
}
}
</script>
Thanks!
CodePudding user response:
When using ref inside setup
you need to change it's value (or read a value) using value
property so all assignments (inside setup
) to filteredPosts
should look like filteredPosts.value = ....
Outside of setup
(lets say in methods
), the filteredPosts
is a normal reactive property of reactive object (component instance) and .value
is not needed (and it is an error to use it)
All this is pretty confusing and one of the reasons why mixing Composition API and Options API is not recommended - just use one or the other.
In your case filteredPosts
can be a simple computed
without the need to manually reassign it every time the input is changed...
const app = Vue.createApp({
setup(){
const searchTerm = Vue.ref('')
let allPosts = [
{
"id": 1,
"title": "Title1",
},
{
"id": 2,
"title": "Title2",
}
]
const filteredPosts = Vue.computed(() => allPosts.filter(post => post.title.toLowerCase().includes(searchTerm.value.toLowerCase())) )
return {
searchTerm,
filteredPosts
}
},
})
app.component('Searchfield', {
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const model = Vue.computed({
get: () => props.modelValue,
set: (newValue) => emit('update:modelValue', newValue)
})
return { model }
},
template: `<input type="text" v-model="model" placeholder="Search...">`
})
app.mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id='app'>
<searchfield v-model="searchTerm"></searchfield>
<div v-for="post in filteredPosts" :key="post.id">
{{ post.title }}
</div>
</div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>