bba278
bba278

Reputation: 419

How to optimize / speed up automatic search in a Vue JS app?

We have implemented a search functionality on an array of around 1200 entries, with 6 filters and a search keyword. As the user selects any of the filters or types a keyword, 'automatic' search is performed filtering down the displayed results. The problem is that the filtered array gets re-rendered slower than expected, leading to the app freezing for a fraction of the second while the content is being rendered. My question is how can the following code be optimized in order to result in a better user experience i.e. while typing a search keyword or selecting filters the results should be displayed quickly or in a manner that would not interrupt the user?

The structure we have is a parent component which contains 2 children: one is a Search bar including filters in the form of dropdowns and the other is the SearchResultsList which renders 4 Bootstrap-Vue b-cards per row until the list is exhausted.

In the parent we render the two children like so:

<b-row
  class="pt-sm-4 p-sm-0 p-2 bg-white"
  no-gutters
>
  <b-col
    sm="12"
    class="px-sm-3 mb-4"
  >
    <!-- People Search Bar -->
    <practice-people-search
      :people="filteredPeopleByKeyword"
      @togglePracticePeopleFiltersEvent="onTogglePracticePeopleFiltersEvent"/>

    <!-- Practice People Search Results List -->
    <practice-people-search-results-list
      :people="filteredPeopleByKeyword"
      :show-practice-people-filters="showPracticePeopleFilters"/>
  </b-col>
</b-row>

We also have a created hook and some computed properties which respectively make a back-end call to acquire the data and filter the incoming array. We first filter the large 1200 entries array using a default filter and/or any other selected filters and then filter the newly created array as the user is typing a keyword, like so

computed: {
    ...mapState('practice', [
      'practicePeopleKeyword',
      'practicePeopleCurrentValue',
      'practicePeopleOfficesValue',
      'practicePeopleGroupsValue',
      'practicePeopleOMGsValue',
      'practicePeopleStatusValue',
      'practicePeoplePositionValue']),
    filteredPeopleByFilters () {
      let filteredPeopleByFiltersArray = []
      for (let i = 0; i < this.people.length; i++) {
        let person = this.people[i]

        let currentResult = (this.practicePeopleCurrentValue.length === 0 || this.practicePeopleCurrentValue.filter((v) => {
          let cr = true
          switch (v.Value) {
            case 0:
              cr = (person.practiceCurrent === 1) && (person.officeCurrent === 1) && (person.groupCurrent === 1)
              break
            case 1:
              cr = (person.practiceCurrent === 0) && (person.started === 1)
              break
            case 2:
              cr = (person.practiceCurrent === 1) && (person.officeCurrent === 0)
              break
            case 3:
              cr = (person.practiceCurrent === 1) && (person.groupCurrent === 0)
              break
            case 4:
              cr = (person.started === 0)
              break
          }
          return cr
        }).length !== 0)
        let officeResult = (this.practicePeopleOfficesValue.length === 0 || this.practicePeopleOfficesValue.filter((v) => { return v.Value === person.mo_ref }).length !== 0)
        let groupResult = (this.practicePeopleGroupsValue.length === 0 || this.practicePeopleGroupsValue.filter((v) => { return v.Value === person.gp_ref }).length !== 0)
        let omgResult = (this.practicePeopleOMGsValue.length === 0 || this.practicePeopleOMGsValue.filter((v) => { return v.Value === person.omg_ref }).length !== 0)
        let statusResult = (this.practicePeopleStatusValue.length === 0 || this.practicePeopleStatusValue.filter((v) => {
          return (v.Value === person.mk_ref) || (v.Value === person.md_type)
        }).length !== 0)
        let positionResult = (this.practicePeoplePositionValue.length === 0 || this.practicePeoplePositionValue.filter((v) => {
          return v.Value === person.ms_ref || ((v.Value === -2) && (person.isPE === 1))
        }).length !== 0)

        if (currentResult && officeResult && groupResult && omgResult & statusResult && positionResult) {
          filteredPeopleByFiltersArray.push(person)
        }
      }
      return filteredPeopleByFiltersArray
    },
    filteredPeopleByKeyword () {
      let filteredPeopleByKeywordArray = []
      for (let i = 0; i < this.filteredPeopleByFilters.length; i++) {
        let person = this.filteredPeopleByFilters[i]

        let searchKeywordResult = (this.practicePeopleKeyword === '' || person.psname.indexOf(this.practicePeopleKeyword) > -1)

        if (searchKeywordResult) {
          filteredPeopleByKeywordArray.push(person)
        }
      }
      return filteredPeopleByKeywordArray
    }
  }

The code in practice-people-search component has computed properties to understand which filters have been selected and store them in the Vuex while practice-people-search-results-list comnponent purely renders the incoming prop :people="filteredPeopleByKeyword"

I hope the above makes sense and would be grateful if anybody has ideas on how the search can be sped up, in order to avoid or at least minimize glitches? Cheers

Upvotes: 2

Views: 1286

Answers (1)

rustyx
rustyx

Reputation: 85371

Filtering 1200 elements shouldn't be noticeable. There must be some inefficiency in the shown code or elsewhere in the application.

Make sure filteredPeopleByFilters() is not invoked every time practicePeopleKeyword is changed, but only when filters change. You can do it with a quick temporary console.log. Same for filteredPeopleByKeyword() - make sure it's invoked only once per update.

Also in if (currentResult && officeResult && groupResult && omgResult & statusResult && positionResult) you are not benefiting from short-circuiting. Change it to something like

    let currentResult = ...
    if (!currentResult) continue;
    let officeResult = ...
    if (!officeResult) continue;
    let groupResult = ...
    if (!groupResult) continue;
    ... etc ...

Write less code. Using built-in functions can often speed up JS code.

For example, try replacing

filteredPeopleByKeyword () {
  let filteredPeopleByKeywordArray = []
  for (let i = 0; i < this.filteredPeopleByFilters.length; i++) {
    let person = this.filteredPeopleByFilters[i]

    let searchKeywordResult = (this.practicePeopleKeyword === '' || person.psname.indexOf(this.practicePeopleKeyword) > -1)

    if (searchKeywordResult) {
      filteredPeopleByKeywordArray.push(person)
    }
  }
  return filteredPeopleByKeywordArray
}

With

filteredPeopleByKeyword () {
  return this.filteredPeopleByFilters.filter(
    person => person.psname.indexOf(this.practicePeopleKeyword) > -1
  )
}

Upvotes: 3

Related Questions