Reputation: 419
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-card
s 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
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