Search⌘ K
AI Features

Crafting a Generic Search Function

Explore how to create a generic search function using TypeScript and React. Learn to write a type-safe function that filters objects by string or number properties and understand how to integrate it into your app for dynamic search capabilities.

Now that our application successfully displays our widgets and people objects, we can start building our first functionality, a generic search function that we’ll call genericSearch.

What is search, really?

In its most abstract form, searching is finding data that matches a query. In our situation, because we have already decided to map over both the widgets and people variables, we could consider our search to just be the call to the JavaScript array filter function before we call map to render the items. With our current setup, that looks something like this:

TypeScript 3.3.4
<>
{widgets.filter((widget) => genericSearch(widget, query).map(...)}
{people.filter((person) => genericSearch(person, query).map(...)}
</>

Our genericSearch function will accept the object itself, as well as some sort of query to use to actually filter for the values.

Writing the genericSearch function

Let’s start writing our genericSearch function. First, we need to create a new folder called utils and then a new file genericSearch.ts. We can start by writing the signature of the function. We know that the function will be generic since it will be able to sort objects of any type. So, we can go ahead and add the <T> and the object, which is of type T.

TypeScript 3.3.4
export const genericSearch = <T>(object: T) => {
}

Why are we using the export const genericSearch syntax instead of export default function genericSearch? Typically the recommendation is to use export const over export default since export const is slightly more flexible than export default. We can rename variables that are imported from export const modules, whereas we can’t with export default .

Our query will be text-based, so we can add that with type string:

TypeScript 3.3.4
export const genericSearch = <T>(object: T, query: string) => {
}

Since this function is called within the filter call, we know it must satisfy the criteria to be a predicate functionA function that can return only true or false, so next, we’ll add a return type of boolean:

TypeScript 3.3.4
export const genericSearch = <T>(object: T, query: string): boolean => {
}

What should we filter with?

The signature of genericSearch is still incomplete. We can’t compare a query of type string to an object of type T, at least not without accessing a property of that object. We’ll be using the TypeScript operator keyof to accept a parameter that can tell us which property to compare to query. We’ll call this parameter property:

TypeScript 3.3.4
export const genericSearch = <T>(object: T, property: keyof T, query: string): boolean => {
}

Writing the body of genericSearch

Now that we know that we’ll be searching with property, we’ll get that value from our object.

TypeScript 3.3.4
export const genericSearch = <T>(object: T, property: keyof T, query: string): boolean => {
const value = object[property];
}

TypeScript won’t give us an error because property quite literally is a keyof T. Now, we can attempt to use our query value and see if the query is included in the value.

TypeScript 3.3.4
export const genericSearch = <T>(object: T, property: keyof T, query: string): boolean => {
const value = object[property];
return value.includes(query);
}

This time, TypeScript gives us an error because it doesn’t know for sure that that value is of type string. In JavaScript, object properties can point to all sorts of things. They can point to both primitive types, like Boolean, Null, Undefined, Number, BigInt, String, and Symbol, or they can point to another nested object entirely! For now, we’ll focus on the properties which are indeed of type string. To enforce this, we’ll use a type guard before calling includes. If this type guard fails, we simply return false since it’s a value type that our function doesn’t handle yet.

TypeScript 3.3.4
export const genericSearch = <T>(object: T, property: keyof T, query: string): boolean => {
const value = object[property];
if (typeof value === "string") {
return value.includes(query);
}
return false;
}

Small improvement to accept numbers

With some creative thinking, we can also allow properties of type number to work with this filter function. We can add number to our type guard, and then, make use of JavaScript’s toString() function to convert it to a string.

TypeScript 3.3.4
export const genericSearch = <T>(object: T, property: keyof T, query: string): boolean => {
const value = object[property];
if (typeof value === "string" || typeof value === "number") {
return value.toString().includes(query);
}
return false;
}

(If the type of value is already string, this is fine, the value will remain as type string.)

Using genericSearch

Now, when we go back in App.tsx, we can actually import and use our new genericSearch function. Since we’re already rendering the titles of the widgets, let’s apply genericSearch to the property title.

TypeScript 3.3.4
{widgets.filter((widget) => genericSearch(widget, "title", query)).map(widget => {
return (
<h3>{widget.title}</h3>
)
})}

Granted, we have no way of changing query right now, but we will see how to wire that up in the coming lessons.

We’ve written an initial genericSearch function that is able to search any properties which yield a type string for any object of type T. However, we’re currently limited to one property, which is a disadvantage. What if we want to search with both the title and description of the widgets objects simultaneously? For example, in the case of the people variable, what if we would like to search with both the firstName and lastName?

That functionality isn’t possible with our current implementation of genericSearch. In the next lesson, we’ll see how we can improve genericSearch to look through multiple properties simultaneously.