Photo by Danial Igdery on Unsplash
Building Fundamental App Functionalities for Beginners: A Framework-Agnostic Guide 1
Pagination, Sorting, Filtering, and Searching
As a beginner getting started with frontend development, there are basic functionalities that you’ll implement in almost all applications you’ll encounter, either to build, upgrade, or debug. Functionalities like Search, Filtering, Pagination, Sorting, CRUD Operations, Error Handling, and Authentication and Authorization are essential concepts to be acquainted with as you progress your learning as a frontend developer. While some of these functionalities are normally handled on the backend, understanding how they work is crucial for their implementation on the frontend.
JavaScript is the frontend development programming language, but several frameworks and libraries are built atop it. Therefore, to foster an unhindered and direct approach to understanding the concepts in this article, the article follows a framework-agnostic pattern. Using vanilla JavaScript to teach the concepts, you will learn how to apply them to any frontend framework or library of your choice and have the same result.
Pagination
In large applications or even a small-scale application with a large database, it is important to paginate the data when displaying them on a page for a better user experience. Rather than having to scroll endlessly to access some data on a page, in a paginated application, a user can easily move from one page to the other, back and forth, and still not lose sight of some important pieces of information placed at the top of the page, if there is. Also, pagination reduces the page load time by rendering the data in bits—10s, 15s, or 20s, not at once. By reducing the amount of data dispatched to the page, the page loads faster and improves the application’s user experience.
Pagination is dividing a large chunk of data into discrete pages with easy navigation from one end to the other. A step-by-step procedure to achieve the process mentioned earlier is to break down the definition into discrete steps.
Implementation
- Collect and store the data in a container that has suitable manipulation methods.
// For most cases, we have the data coming from the server in an array, // which is the best datatype for storing and accessing data.
// Initially declare a variable to store the data from the API
let users = [];
// after an API GET request, get your response and store assign it to // the predefined variable:
console.log(res)
=> {
error: false,
status: 200,
message: "User data retrieved successfully!",
data: {
name: "users",
noOfUsers: 240,
data: [
{
firstName: "John",
lastName: "Doe",
gender: "Male",
age: 24
},
{
firstName: "Jane",
lastName: "Doe",
gender: "Female",
age: 25
},
...
]
},
}
users = res?.data?.data
//NB: The res came as an object, but the data to be used in the page is res?.data?.data which is an array
- Declare the calculation variables.
// Now that you have the data, divide it into chunks
// For this, you need some new variables to store the number of items // returned from the server, the amount you want to have per page, and
// the current page
const itemsCount = users?.length // using the array.length method
const pageSize = 15 // You need 15 users max per page
let currentPage = 1 // 1 because the page starts on the first page.
// Based on these 2 variables, determine the estimate of the number of // pages to expect and spread the value out in an array. This data comes in handy for your pagination component.
const pagesCount = Math.ceil(itemsCount / pageSize);
const pages = [];
let count = 0
for (let page = 1, page < pagesCount.length, page++) {
count++;
pages.push(count)
};
// At the end of this loop, pages has an array of items 1 to the
// pagesCount value.
- Paginate the data.
// Now, you need to paginate the users data to match the no required
// for each page
function paginate(data, pageCount, currentPage) {
const start = ((currentPage - 1) * pageCount) + 1;
const end = (pageCount * currentPage) + 1;
return data.slice(start, end)
}
const paginatedUsers = paginate(users, pagesCount, currentPage);
- Implement a function to move from one page to another.
/* This function is simple and it's applied to every navigation
button in the pagination component, but in special manners. If a pageNumber argument is passed, it updates the currentPage and set it to the pageNumber, and in so doing, the paginatedUsers array is updated. If the "prev" string is passed, it subtracts one from the currentPage value, and finally, if no argument is passed, it adds one to the currentPage value */
function handleNavigation(value) {
if (typeof value === "string" && value === "prev") currentPage--;
if (typeof value === "number") currentPage = page;
currentPage++
}
for the previous button, you pass
<button onClick={() => handleNavigation("prev")}>Prev</button>
for the pages button, you pass:
<button onClick={() => handleNavigation(page)}>{page}</button>
and for the next button, you pass nothing:
<button onClick={handleNavigation}>Next</button>
The above code samples assume you choose to have next and prev among your navigation buttons alongside the buttons numbered according to the page they represent. If the pages are not too many, like about 5 or 6, you can show all the numbered navigation buttons. It is ideal to limit the number of navigation buttons displayed and have a few with the prev and next buttons in an app of 10 or more pages. However, you should know that there’s an extra layer of implementation to achieve that UI, which is not covered in this article.
Following these simple steps in your application, regardless of the framework or library you choose, will help you easily build and set up pagination.
Sorting
One of many times, you might build an application that requires your data to be arranged based on some basic criteria, say, descendingly, ascendingly, or by the latest or earliest date, which are common, or even based on some more specific criteria like price, newly updated, category, popularity (for an e-commerce store) or author, for an online bookstore, and more others. More often than not, this arrangement is not rigid and is usually dictated by the client’s events. A customer might decide to check a collection of books by choosing to sort by the author’s name or the year the book was published, changing the criteria as often as desired. Such a data arrangement cannot be static but dynamic to ensure a good user experience. It would be a poor experience for a client to access data on your site and see that the arrangements have been made, and there can not be any form of adjustment to it.
Sorting is data organization for ease of access and better user understanding. It’s a crucial functionality to be present in any application that deals with a large volume of data that the users need to interact with.
Sorting data, irrespective of what they are, bears down to arranging them ascendingly and descendingly, and there’s just the perfect JavaScript array method for sorting—array.sort().
// sorting ascendingly
const originalArr = [];
const ascSortedArr = originalArr.sort((a, b) => a.item - b.item);
const desSortedArr = originalArr.sort((a, b) => b.item - a.item);
Often, you get a table data array and need to sort the data in a descending order. From the object properties, what should be the item to sort to achieve your goal? In every array of data from the backend, the date each data object item was created is passed and named as createdAt, a naming convention the database used decides. With the date at your disposal, you can easily sort the array descendingly.
const userData = [
{
firstName: "Sarah",
lastName: "Sarah",
username: "RealSarahDoe",
createdAt: "2024-02-20T06:24:30.000000Z"
},
{
firstName: "Jane",
lastName: "Doe",
username: "RealJaneDoe",
createdAt: "2024-02-21T08:21:30.000000Z"
},
{
firstName: "John",
lastName: "Doe",
username: "RealJohnDoe",
createdAt: "2024-02-22T08:51:30.000000Z"
},
]
const desUserData = userData.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
console.log(desUserData)
Result:
const userData = [
{
firstName: "John",
lastName: "Doe",
username: "RealJohnDoe",
createdAt: "2024-02-22T08:51:30.000000Z"
},
{
firstName: "Jane",
lastName: "Doe",
username: "RealJaneDoe",
createdAt: "2024-02-21T08:21:30.000000Z"
},
{
firstName: "Sarah",
lastName: "Sarah",
username: "RealSarahDoe",
createdAt: "2024-02-20T06:24:30.000000Z"
},
]
// By passing createdAt as an argument of the new Date() method, the dates are formatted easily for a precise calculation by the array.sort() method
- NB: You could also use
updatedAt
instead ofcreatedAt
, which tends to be more precise. The choice depends on the app’s requirements.
Arranging dates is one of the easiest and most common uses of the JavaScript array.sort() method but you might need to sort a table data array based on other criteria. The approach remains the same but there might be some additional JavaScript methods to use in special cases, just like when sorting based on dates. For instance, if you want to sort by category and the category is defined in languages whose texts contain special characters, you will need to use the JS localeCompare() method to get a more precise sorting result.
const arr = [...];
const sortedByCategory = arr.sort((a, b) => a.category.localeCompare(b.category));
localeCompare()
takes more optional parameters to specify the locale and options for comparison. These parameters could help give a better accurate result. You can read them up on MDB
Also, when dealing with an array of data that you do not want to mutate, appending arr.slice()
to the sort method does the trick
const arr = [...];
const sortedPriceArr = arr.slice().sort((a, b) => b.price - a.price);
In the above code samples, the alphabets a and b are used but it’s important to know that you don’t necessarily need to use them; you could use x and y or p and q, they don't matter, but their arrangement does. To get a descendingly sorted array, the second argument must come first, b.item before a.item, and vice versa for an ascendingly sorted array.
Filtering
Filtering is by far the simplest functionality to implement and is also the underlying function in other functionalities that you’ll learn about in the future. Filtering allows users to focus on specific data by eliminating unwanted data, thereby enhancing user experience.
The filtering functionality is easy to define as it has a JS array method made specifically for it—array.filter(). However, how you define it determines the data it eliminates and the ones it reveals to your users. Filtering operates by comparison. You compare an object item in an array with a value. If the comparison is truthy, all the object items in the array that match are returned, while the rest are removed. Filtering does not mutate the original array, rather it creates a shallow copy of the original array and manipulates it.
// Filter by status
const arr = [
{
firstName: "John",
lastName: "Doe",
status: "active",
},
{
firstName: "Janet",
lastName: "Doe",
status: "inactive",
},
]
const activeUsers = arr.filter(item => item.status === "active")
console.log(activeUsers)
Result:
[
{
firstName: "John",
lastName: "Doe",
status: "active",
},
]
As simple as it appears, additional conditionals and other JS array methods could be used to perform complex operations. The next functionality will provide an example.
Searching
In an application with a large dataset, you need to every data easily accessible by the user despite the data’s location in the array. Even if pagination has already been implemented in your application, there’s still a need for a search functionality to allow the users quick access to specific data. If a user has to move from one page to another to get data, it denotes an application with a poor user experience. Implementing the search functionality allows the users to easily type in the data they need to access in the search input element and have the data pop up if it exists.
The underlying mechanism of the search functionality is the filter functionality and the former is a type that requires extra conditionals and array methods to search precisely in some cases.
For a table of users’ data with their first name, last name, and user name, among other data, a developer needs to define a function that enables one of the data the user will type in the search bar to match the data of users in the conditionals criteria. The user might search based on the username or the first name or the last name, and if you as a developer implement a filter function that only corresponds with one of the three instead of all, you have not implemented an efficient search functionality. Another interesting aspect is that the user might not know what exactly to find or even the correct spelling of the name and will type in the search bar randomly, hoping to get lucky. In cases like this, you need to constantly update the filtered array upon each letter entry and deletion from the search bar. For these reasons, this functionality can be complex sometimes.
const userData = [
{
firstName: "John",
lastName: "Doe",
username: "bigjohn",
status: "active",
},
{
firstName: "Janet",
lastName: "Doe",
username: "Janedoe",
status: "inactive",
},
]
function handleSearch(e) {
let value = e.target.value;
value = value.toLowerCase();
const filteredUsers = userData.filter(item => {
const firstName = item.firstName.toLowerCase();
const lastName = item.lastName.toLowerCase();
const username = item.username.toLowerCase();
return [firstName, lastName, username].includes(value)
});
return filteredUsers;
}
- Ensure you attach the handleSearch function to the search input element’s onChange attribute.
In the above example, you initially convert all the items to be compared to lowercase, then filter the data using JS arr.includes() method. This method is used instead of direct comparison because you’re comparing here with more than one object property and value is constantly updated as the user types. With this approach, the filtered data returned will increase and reduce in length till the user types in a value that matches only one array object item property. If the value does not match any item, the filtered data returns an empty array.
Based on your requirements, you can use any JS array method and conditionals that help you make a precise comparison in the search functionality.
Conclusion
This article introduces you to the first four basic functionalities—pagination, sorting, filtering, and search. They are the underlying functionalities of any complex app operation and can be tweaked to match your app’s requirements, just as illustrated by the filtering functionality in the search functionality. A comprehensive understanding of these functionalities is a great tool in your developer’s toolbox and can help make your work easier.
The next 3 functionalities are quite advanced and will be discussed in the second part of this article.
NB: If you know a better way of achieving these basic functionalities, kindly add them to the comment section.
Follow me on X @theDocWhoCodes to learn more about what I do and write about.