Search code examples
vue.jsvuejs3vue-composition-apivue-script-setup

Creating a Search Input Filter with Computed in Vue 3


I've worked through this guide to create a search filter input field but can't figure out how to correctly implement computed in the v-model.

I've transformed the code from the guide into:

<template>
  <div id="table-cms" class="table-cms">
    <input class="search-field textfield-closed" type="search" placeholder="Search" v-model="filter">
    <p>{{ filter }}</p>
    <p>{{ state.array }}</p>
  </div>
</template>


<script setup>
import {computed, reactive} from "vue";

const state = reactive({
    search: null,
    array: [
        {id: 1, title: 'Thanos', content: '123'},
        {id: 2, title: 'Deadpool', content: '456'},
        {id: 3, title: 'Batman', content: '789'}
    ]
})

const filter = computed({
    get() {
        console.log('check1')
        return state.search
    },
    set() {
        if (state.search) {
            console.log('check2a')
            return state.array.filter(item => {
                return state.search
                    .toLowerCase()
                    .split(" ")
                    .every(v => item.title.toLowerCase().includes(v))
            });
        } else {
            console.log('check2b')
            return state.array;
        }
    }
})
</script>

But the console shows:

check1
check2b
check2b
check2b
...

This means that computed gets executed but it doesn't enter if (state.search) {} (the actual filter). Displaying state.array does render the initial array but does not get updated by typing different titles in the input field:

<p>{{ state.array }}</p>

rendering:

[
  {
    "id": 1,
    "title": "Thanos",
    "content": "123"
  },
  {
    "id": 2,
    "title": "Deadpool",
    "content": "456"
  },
  {
    "id": 3,
    "title": "Batman",
    "content": "789"
  }
]

What am I doing wrong?


Solution

  • You have to use state.search as the v-model on your input:

    <input class="search-field textfield-closed" type="search" placeholder="Search" v-model="state.search">
    

    Otherwise it stays null forever because it is not changing which causes the code to skip the if statement.

    Also you don't need a setter in your computed filter.

    <template>
        <div id="table-cms" class="table-cms">
            <input
                class="search-field textfield-closed"
                type="search"
                placeholder="Search"
                v-model="state.search"
            />
            <p>{{ state.array }}</p>
        </div>
    </template>
    
    <script setup>
    import { computed, reactive } from "vue";
    
    const state = reactive({
        search: null,
        array: [
            { id: 1, title: "Thanos", content: "123" },
            { id: 2, title: "Deadpool", content: "456" },
            { id: 3, title: "Batman", content: "789" },
        ],
    });
    
    const filter = computed(() => {
        if (state.search) {
            //console.log('check2a')
            return state.array.filter((item) => {
                return state.search
                    .toLowerCase()
                    .split(" ")
                    .every((v) => item.title.toLowerCase().includes(v));
            });
        } else {
            console.log("check2b");
            return state.array;
        }
    });
    </script>