skip to Main Content

Here’s my issue. I created a tool with vue.js and the WordPress API to search through the search endpoints for any keyword and display the result. So far so good, everything is working, except for a bug that I spotted.

Here’s the deal:

const websiteurl = 'https://www.aaps.ca'; //yourwebsite or anything really
var vm = new Vue({
    el: '#blog-page',
    data: {
        noData: false,
        blogs: [],
        page: 0,
        search: '',
        totalPagesFetch: "",
        pageAmp: "&page=",
        apiURL: `${websiteurl}/wp-json/wp/v2/posts?per_page=6`,
        searchbyid: `${websiteurl}/wp-json/wp/v2/posts?per_page=6&include=`,
        searchUrl: `${websiteurl}/wp-json/wp/v2/search?subtype=post&per_page=6&search=`,
    },
    created: function () {
        this.fetchblogs();
    },
    methods: {
        fetchblogs: function () {
            let self = this;
            self.page = 1;
            let url = self.apiURL;
            fetch(url)
            .then(response => response.json())
            .then(data => vm.blogs = data);
        },
        searchonurl: function () {
            let ampersand = "&page=";
            searchPagination(1, this, ampersand);
        },
    }
});

function searchPagination(page, vm, pagen) {

    let self = vm;
    let searchword = self.search.toLowerCase();
    let newsearchbyid = self.searchbyid;
    let url;

    self.page = page;

    url = self.searchUrl + searchword + pagen + self.page;

    self.mycat = 'init';

    fetch(url)
    .then(response => {

        self.totalPagesFetch = response.headers.get("X-WP-TotalPages");
        return response.json();
    })
    .then(data => {
        
        let newid = [];
        data.forEach(function (item, index) {
            newid.push( item.id );
        });
        if (newid.length == 0) {
            return newsearchbyid + '0';
        } else {
            return newsearchbyid + newid;
        }
    })
    .then(response2 => {
        return fetch(response2)
    })
    .then(function(data2) {
        return data2.json();
    })
    .then(function(response3) {
        console.log(response3)
        if (response3.length == 0) {
            vm.noData = true;
            vm.blogs = response3;
        } else {
            vm.noData = false;
            vm.blogs = response3;
        }
    })
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="lazyblock-blogs testblog" id="blog-page">
    <div class="container">
        <div class="row controls">
            <div class="col-md-12">
                <div class="search-blog">
                    <img height="13" src="" alt="search">
                    <input id="sb" type="text" v-model="search" @keyup="searchonurl" placeholder="search">
                </div>
            </div>
        </div>

        <div class="row">

            <div class="col-md-4" v-for="(b, index) in blogs">
                <div class="h-100 box" v-cloak>
                    <img width="100%" v-bind:src=b.featured_image_url>
                    <a v-bind:href="b.link">
                        <h3 v-html=b.title.rendered></h3>
                    </a>
                    <div v-html=b.excerpt.rendered></div>
                    <p class="read-more"><a v-bind:href="b.link">read more</a></p>
                </div>
            </div>

            <div class="no-data" v-if="noData">
                <div class="h-100">
                    No post
                </div>
            </div>

        </div>

    </div>
</div>

I’m using a keyup event which is causing me some problems because it works, but in same cases, for example, if the user is very fast to type characters and then suddenly he wants to delete the word and start again, the response for the API has some sort of lag.

The problem is that I guess that the Vue framework is very responsive (I create a variable call search that will update immediately) but the API call in the network is not (please check my image here):

enter image description here

This first image appears if I type lll very fast, the third result will return nothing so it is an empty array, but if I will delete it immediately, it will return an url like that: https://www.aaps.ca//wp-json/wp/v2/search?subtype=post&per_page=6&search=&page=1 which in turn should return 6 results (as a default status).

The problem is that the network request won’t return the last request but it gets crazy, it flashs and most of the time it returns the previous request (it is also very slow).

Is that a way to fix that?

I tried the delay function:

function sleeper(ms) {
  return function(x) {
    return new Promise(resolve => setTimeout(() => resolve(x), ms));
  };
}

and then I put before the then function:

.then(sleeper(1000))

but the result is the same, delayed by one second (for example)

Any thought?

3

Answers


  1. You could use debounce, no call will leave until the user stop typing in the amount of time you chose

    function debounce(func, wait, immediate) {
      var timeout;
      return function() {
        var context = this, args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        }, wait);
        if (immediate && !timeout) func.apply(context, args);
      };
    }
    
    // in your "methods" (I put 1000ms of delay) :
    searchonurl: function () {
      let ampersand = "&page=";
      debounce(searchPagination, 1000)(1, this, ampersand);
    }
    
    Login or Signup to reply.
  2. One of best ways is to use Debounce which is mentioned in this topic

    Or use a function and combine it with watch. Follow these lines:

    In mounted or created make an interval with any peroid you like (300 etc.) define a variable in data() and name it something like searched_value. In interval function check the value of your input and saerch_value, if they were not equal (===) then replace search_value with input value. Now you have to watch search_value. When it changed you call your api.

    I use this method and works fine for me. Also it`s managable and everything is in your hand to config and modify.

    ===== UPDATE: =====

    A simple code to do what I said above

    <template>
        <div>
            <input type="search" v-model="search_key">
        </div> </template>
    
    <script> export default {
        name: "SearchByApi",
        data() {
            return {
                search_key: null,
                searched_item: null,
                loading: false,
                debounceTime: 300,
            }
        },
        created() {
            this.checkTime()
            const self = this
            setInterval(function() {
                self.checkTime()
            }, this.debounceTime);
        },
        watch: {
            searched_item() {
                this.loadApi()
            }
        },
        methods: {
            checkTime() {
                if (this.searched_item !== this.search_key && !this.loading) {
                    this.searched_item = this.search_key
                }
            },
            loadApi() {
                if (!this.loading && this.searched_item?.length > 0) {
                    this.loading = true
                    const api_url = 'http://api.yourdomain.com'
                    axios(api_url, {search: this.searched_item}).then(res => {
                        // whatever you want to do when SUCCESS
                    }).catch(err => {
                        // whatever you want to do when ERROR
                    }).then(res => {
                        this.loading = false
                    })
                }
            }
        }
    }
    </script>
    
    Login or Signup to reply.
  3. This is the case for debounced function. Any existing implementation can be used, e.g. Lodash debounce. It needs to be declared once per component instance, i.e. in some lifecycle hook.

    That searchPagination accepts this as an argument means that something went wrong with its signature. Since it operates on component instance, it can be just a method and receive correct this context:

    methods: {
      searchPagination(page, pagen) {
        var vm = this; 
        ...
      },
      _rawsearchonurl() {
        let ampersand = "&page=";
        this.searchPagination(1, ampersand);
      }
    },
    created() {
      this.searchonurl = debounce(this._rawsearchonurl, 500);
      ...
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search