Filter, Sort, and Search Arrays with JavaScript
To add that next level user experience to your frontend, you need speed. Areas of your app that always need just a little optimization are ones that display and compute data. You have a decision to make. Do you load more data from the API or do you allow the user to manipulate and filter the data with JavaScript? In this tutorial, we will assume you are making the choice to let the user manipulate pre-loaded data with JavaScript. We will step through how to filter, sort, and search arrays with JavaScript.
Why Would You Do This with JavaScript?
Wouldn’t you want to make an API request instead? There’s a few reasons. First, maybe you can only narrow down so far with your API. You still want the users to be able to filter results. Second, maybe you don’t need to make an API request and have a medium sized amount of data that loads into memory. Yea, it happens. Or finally, maybe you are dealing with a rate-limited or slow API and you want to make a larger request and filter the data client side. These are all reasonable scenarios. With a few methods you can take a static table or grouping of content and filter it efficiently with JavaScript.
In this tutorial we are going to assume you have your data stored in memory. In memory, you will either have it stored in an array that’s a single level or an array of objects. The examples in this tutorial will work for vanilla JavaScript or any JavaScript framework. In fact, the examples might even work better in reactive frameworks like Vue or React. I’ll provide vanilla examples and ones with Vue. Let’s get started.
Filtering Arrays
When we think of filtering an array, we want to get a subset of an existing array. In all of these examples, the single level arrays will probably be less used than array of objects. Working with an API we tend to get data that mirrors a database record. That’s great, I’ll provide both examples. Since we want to get a subset of an array, we will be returning the filtered array instead of modifying the source.
Single Level Array
Let’s say we want to start small and have a flat level array of numbers. Our array should look like:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
Pretty basic right? Let’s say we wanted to filter out all of the even numbers and return that as an array. Let’s take a look.
JavaScript
In vanilla JavaScript, I’d do something like this:
function filterEvenNumbers(){
return numbers.filter( number => number % 2 == 0 );
}
Pretty slick! All arrays have a simple filter method that allows you run a simple filter on your array. The first parameter is the value of the array as we iterate over it. The arrow is a callback function that returns true
or false
if the filter is matched. So if number % 2 == 0
then it’s even and we return true
. In most scenarios, you’d probably want to update a table, or make some UI changes. In straight JavaScript, you’d take the response from this method and use it to display the data instead of the full numbers
array. With VueJS, since it’s reactive, you can do some pretty awesome maneuvers that make this super efficient.
VueJS
Let’s perform the same filtering in VueJS, but do it through a computed value.
<script setup>
import { computed, ref } from 'vue';
const numbers = ref([1, 2, 3, 4, 5, 6, 7, 8, 9]);
const filterEvenNumbers = ref(true);
const filteredNumbers = computed(() =>
filterEvenNumbers.value
? numbers.value.filter( number => number % 2 == 0 )
: numbers.value;
);
</script>
What this allows us to do is take into account whether the user wants to turn on or off filtering of even numbers (const filterEvenNumbers = ref(true)
). We then return either all of the numbers, or the filtered sub-set of numbers. That way we can display or work with filteredNumbers
and then have a switch that the user can toggle. The computed()
property handled by Vue will reactively update our display depending on what the user needs. Pretty slick! It gets even more functional when you can filter object arrays with JavaScript.
Array of Objects
More than likely, when working with an API you will receive an array of objects as a response. These map to the resource on the API and contain a ton of properties. You can filter these just as easily as a flat array. Let’s use an array of songs.
let songs = [
{
"name": "Anthem-Emancipator.mp3",
"format": "mp3",
"title": "anthem",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "12"
},
{
"name": "DuskToDawn-Emancipator.mp3",
"format": "mp3",
"title": "Dusk to Dawn",
"artist": "Emancipator",
"album": "Dusk to Dawn",
"track": "5"
},
{
"name": "FirstSnow-Emancipator.mp3",
"format": "mp3",
"title": "first snow",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "9"
}
];
JavaScript
I love a little Emancipator. For this example, let’s get all the songs that are on the soon it will be cold enough
album.
function filterAlbum( album ){
return songs.filter( song => song.album == album );
}
This little function takes the string of the album we want to filter. For our example it’d be soon it will be cold enough
. Then we run the same filter method as before, except we reference the key of song.album
. You can run these checks on objects just as easily as a flat array. The first parameter is the object that the iterator is up to. The arrow function returns true
if the value matches what we are looking for. We just have to reference a nested key
with an object!
VueJS
Now with VueJS this should look very similar. We set an album
ref that can be a v-model
on an input or select and filter any songs that match what is entered. We use the same type of concept with the objects as the flat array. If there is nothing to filter, return all of the songs. If there is a filter, filter what the user is looking for. The display can be reactively set through user input and be ready to go!
<script setup>
import { computed, ref } from 'vue';
const songs = ref([
{
"name": "Anthem-Emancipator.mp3",
"format": "mp3",
"title": "anthem",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "12"
},
{
"name": "DuskToDawn-Emancipator.mp3",
"format": "mp3",
"title": "Dusk to Dawn",
"artist": "Emancipator",
"album": "Dusk to Dawn",
"track": "5"
},
{
"name": "FirstSnow-Emancipator.mp3",
"format": "mp3",
"title": "first snow",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "9"
}
]);
const filterAlbum = ref('');
const filteredSongs = computed(() =>
filterAlbum.value != ''
? songs.value.filter( song => song.album == album.value )
: songs.value;
);
</script>
To be honest, with the “exact matches” it’d probably work better through a select box or a number value. For more information on array filtering, MDN has a few more examples.
You might have noticed a minor caveat with filtering. If you were to enter the beginning of a value in this method (i.e. “soon it will”), nothing will return until it’s a full match. In the next section we will go through “searching” that will work perfect for string values. Searching for strings is a more advanced form of filtering. You want to eliminate any values from the displayed content that don’t match the user’s query. Luckily, we can update our previous example to search string values in an array.
Searching Arrays
Let’s use our songs array for this example. It’s the most likely scenario since there are actual string values you can search. However, if you had a 1 dimensional array, the concepts would remain the same.
JavaScript
To search full text in an array, let’s extend our filterAlbum()
method to be the following:
function filterAlbum( album ){
return songs.filter( song => song.album.toLowerCase().search( album.toLowerCase() ) > -1 );
}
It’s just a little more complex than before so let’s break it down. The first part should look the same. We are running the .filter()
method on our songs
array. With that, we are accessing the individual song that’s being iterated and a callback function. However, we are now checking song.album.toLowerCase().search()
. This is quite the change!
First, we are calling the toLowerCase()
method on the album
. The reason we do this is we want to eliminate discrepancies for capitalization. JavaScript will take capital letters into account which can lead to un-wanted results.
Next is where the magic happens. We call the .search()
method and pass our parameter, album
also modified with .toLowerCase()
. The .search()
method searches a string for the index of the parameter. If the parameter is found, it returns the index of where it’s located within the string. If the parameter is not found, it returns -1
. That’s why we check to see if the value is greater than -1
to return true from our filtered array.
You can do some really advanced scenarios with the .search()
method combined with regular expressions. MDN has some more on this method as well.
VueJS
Implementing searching with VueJS is even more powerful. You can live update the UI based off of what the user enters in an input box. This reactivity can lead to some super powerful interfaces. To update our VueJS implementation, update the code to be:
<script setup>
import { computed, ref } from 'vue';
const songs = ref([
{
"name": "Anthem-Emancipator.mp3",
"format": "mp3",
"title": "anthem",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "12"
},
{
"name": "DuskToDawn-Emancipator.mp3",
"format": "mp3",
"title": "Dusk to Dawn",
"artist": "Emancipator",
"album": "Dusk to Dawn",
"track": "5"
},
{
"name": "FirstSnow-Emancipator.mp3",
"format": "mp3",
"title": "first snow",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "9"
}
]);
const filterAlbum = ref('');
const filteredSongs = computed(() =>
filterAlbum.value != ''
? songs.value.filter( song => song.album.toLowerCase().search( album.value.toLowerCase() ) > -1 )
: songs.value;
);
</script>
It should look exactly the same, but behind the scenes with the VueJS reactivity system it’s even more powerful. If a user enters a value into an input field with the v-model
set to filterAlbum
, any change will be reactively shown. Since it’s a computed value, this happens automatically. Just make sure your display shows filteredSongs
. Like the last example, if there’s nothing set with the filter, we return all songs.
The final array method we will be working with is sorting. Instead of returning a new array, we will be sorting the actual array. Perfect for table headers and alphabetization.
Sorting Arrays
The last subject we are going through is the sorting of arrays. This is perfect for ordering numerically, alphabetically, or by whatever means you wish. I’ve used this functionality to add sortable table headers to my apps, order search results by computed fields, etc. For sorting we will be calling the .sort()
method on our array which accepts a callback method that handles our sorting. This method takes two values to compare. If the first value is before the second value, the method should return -1
. If the values match, return 0
and if the second value is sorted before the first value return 1
. When all is said and done, you will have your array sorted the way you want.
In JavaScript, you can sort an array by property or in a single level. For a single level array, let’s randomize the order a little bit:
let numbers = [4,2,7,5,9,1,8,6,3];
Being a flat level array, you can just call numbers.sort();
and it will order the numbers from least to greatest. What if you want it the opposite direction? What if you want the result to be highest-to-lowest numbers? Let’s add our first callback like this:
numbers.sort( function( a, b ){
if( a > b ){
return -1;
}
if( a == b ){
return 0;
}
if( a < b ){
return 1;
}
});
With the callback method, we are able to reverse the order of sorting. This allows us to make the sorting even more dynamic based off of user preferences. Say you have a variable that holds the definition of which direction to sort. We can then add that to our callback and order it based on what the user selects:
let direction = 'DESC';
numbers.sort( function( a, b ){
if( a > b ){
return direction == 'DESC' ? -1 : 1;
}
if( a == b ){
return 0;
}
if( a < b ){
return direction == 'DESC' ? 1 : -1;
}
});
See how this can become more powerful? Wait until we get to sorting an array by property! The user now has full control over how they wish to sort their array. With VueJS, this can happen reactively. First, let’s look at how we would do sorting based on property.
If we use our songs array from the first example, let’s say we want to sort alphabetically by name:
songs.sort( function( a, b ){
let aName = a.name.toLowerCase();
let bName = b.name.toLowerCase();
if( aName > bName ){
return 1;
}
if( aName == bName ){
return 0;
}
if( aName < bName ){
return -1;
}
});
The main difference between sorting by property in an array of objects and by just flat level, is you can reference the property in the sorting method. A good rule of thumb as well, when working with strings is to convert the case to be either upper or lower. That’s why we grab the property and set it to a temporary variable and convert it .toLowerCase()
before running our sorting method.
You can actually get fairly complex with these sorting methods, but beware. At some point you will have to make the decision to make another API request or do the sorting client side. If the sorts get too complex, leave it to the API to create some cached, sortable data set that loads really quickly.
Let’s run the above examples in VueJS.
VueJS
Remember, instead of returning a new array, we are sorting the array that we already have. So within VueJS, we can sort our numbers array like this:
<script setup>
import { computed, ref } from 'vue';
const numbers = ref([4,2,7,5,9,1,8,6,3]);
const sortNumbers = () => {
numbers.value.sort(function( a, b ){
if( a > b ){
return -1;
}
if( a == b ){
return 0;
}
if( a < b ){
return 1;
}
} );
}
</script>
When you call the sortNumbers()
method, the array will be sorted, but also available reactively. So your display should update accordingly which is super convenient! I like to do this in a method instead of a computed value since we are sorting a value that we already have, not filtering and returning a different value. However, you could use computed()
just like we did above.
If you wanted to add a variable that held the state of whether the user wanted the array to be sorted ASC
or DESC
you could the following:
<script setup>
import { computed, ref } from 'vue';
const numbers = ref([4,2,7,5,9,1,8,6,3]);
const direction = ref('DESC');
const sortNumbers = () => {
numbers.value.sort(function( a, b ){
if( a > b ){
return direction.value == 'DESC' ? -1 : 1;
}
if( a == b ){
return 0;
}
if( a < b ){
return direction.value == 'ASC' ? 1 : -1;
}
} );
}
</script>
Now the user can quickly set the direction they wish to sort the numbers within your app. Let’s wrap up with a quick example of sorting an array by property with VueJS:
<script setup>
import { computed, ref } from 'vue';
const songs = ref([
{
"name": "Anthem-Emancipator.mp3",
"format": "mp3",
"title": "anthem",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "12"
},
{
"name": "DuskToDawn-Emancipator.mp3",
"format": "mp3",
"title": "Dusk to Dawn",
"artist": "Emancipator",
"album": "Dusk to Dawn",
"track": "5"
},
{
"name": "FirstSnow-Emancipator.mp3",
"format": "mp3",
"title": "first snow",
"artist": "emancipator",
"album": "soon it will be cold enough",
"track": "9"
}
]);
const sortSongs = () => {
songs.sort( function( a, b ){
let aName = a.name.toLowerCase();
let bName = b.name.toLowerCase();
if( aName > bName ){
return 1;
}
if( aName == bName ){
return 0;
}
if( aName < bName ){
return -1;
}
});
};
</script>
The same concept as above, just with VueJS your display doesn’t need to be updated through another method, it will happen reactively.
Conclusion
These simple array functions allow us to quickly react to user input without the need for an API request and filter. They are perfect for those small areas in your app where you want that extra level of functionality. Granted you will have to make the call on when to filter an array through JavaScript or make another API request. But knowing the capabilities allows you to make better decisions as a developer. If you have any questions, feel free to reach out on Twitter (@danpastori)! Make sure to join our mailing list to get alerted of new articles straight to your inbox!