Progressive Search in React Native

Maria James
5 min readOct 11, 2023

--

Progressive search is a crucial feature in modern applications, enhancing the user experience by providing real-time, dynamic search results. It allows users to find information quickly and efficiently. In this comprehensive guide, we will go through the process of implementing progressive search in your React Native project, step by step.

source : https://github.com/mariajz/weather-app

Lets get started! 🚀

Step 1: Implementing a Basic Search Bar

import { useState } from "react";
import { TextInput, View } from "react-native";

function App() {
const handleOnChangeText = (text) => {
setInput(text);
};

const [input, setInput] = useState("");

return (
<View>
<TextInput
placeholder="Enter city name"
onChangeText={handleOnChangeText}
value={input}
style={{
backgroundColor: "grey",
width: 300,
height: 50
}}
/>
</View>
);
}

export default App;

Step 2 : Create the location fetch Api / helper

Here i am using a sample master data with a list of cities for demo purpose, you can refer this link to understand how to make an actual location fetch api call.

const cityData = {
A: ["Amsterdam", "Athens", "Atlanta", "Adelaide"],
B: ["Berlin", "Barcelona", "Beijing", "Boston"],
C: ["Cairo", "Chennai", "Chicago", "Copenhagen"],
D: ["Dublin", "Delhi", "Denver", "Detroit"],
E: ["Edinburgh", "Edmonton", "Eindhoven", "Essen"],
F: ["Florence", "Frankfurt", "Fort Worth", "Fresno"],
G: ["Geneva", "Guangzhou", "Glasgow", "Greenville"],
H: ["Helsinki", "Houston", "Hong Kong", "Hyderabad"],
I: ["Istanbul", "Indore", "Irvine", "Incheon"],
J: ["Jakarta", "Johannesburg", "Jaipur", "Jeddah"],
K: ["Kyoto", "Karachi", "Kolkata", "Kazan"],
L: ["Lima", "London", "Los Angeles", "Lagos"],
M: ["Mumbai", "Moscow", "Madrid", "Melbourne"],
N: ["New York", "Nairobi", "New Delhi", "Naples"],
O: ["Oslo", "Ottawa", "Oklahoma City", "Orlando"],
P: ["Paris", "Prague", "Perth", "Philadelphia"],
Q: ["Quito", "Quebec City", "Qingdao", "Quanzhou"],
R: ["Rome", "Rio de Janeiro", "Riyadh", "Rotterdam"],
S: ["Sydney", "Seoul", "Shanghai", "San Francisco"],
T: ["Tokyo", "Toronto", "Taipei", "Tianjin"],
U: ["Ulaanbaatar", "Utrecht", "Uberlandia", "Uppsala"],
V: ["Vienna", "Vancouver", "Venice", "Valencia"],
W: ["Warsaw", "Washington", "Wellington", "Wuhan"],
X: ["Xiamen", "Xi'an", "Xining", "Xinyang"],
Y: ["Yerevan", "Yokohama", "Yiwu", "Yichang"],
Z: ["Zurich", "Zhengzhou", "Zibo", "Zigong"]
};

export default cityData;

We’ll now write a helper function called searchCities. This function takes the search input, queries the city master data, or makes the location fetch API call, and then returns the results.

import cityData from "./CityMasterData";

const searchCities = (query) => {
const results = [];
const queryUpperCase = query.toUpperCase();

for (const letter in cityData) {
if (letter === queryUpperCase) {
results.push(...cityData[letter]);
} else if (queryUpperCase.startsWith(letter)) {
results.push(
...cityData[letter].filter((city) =>
city.toUpperCase().startsWith(queryUpperCase)
)
);
}
}

return results;
};

export default searchCities;

Step 3: Displaying Search Results

Here, I’ve implemented a basic dropdown view to display the data. You’re welcome to customize the dropdown according to your specific requirements.

{results.length > 0 && (
<View
style={{
width: 400,
borderRightWidth: 1,
borderRadius: 5
}}
>
{results.map((location, index) => {
return (
<React.Fragment key={index}>
<Text>{location}</Text>
<View
style={{
borderWidth: 0.5,
borderColor: "grey"
}}
/>
</React.Fragment>
);
})}
</View>
)}

Step 4: Integrate the helper function:

Next, we can incorporate the searchCities helper function to be triggered upon user input changes.

import React, { useState } from "react";
import { TextInput, Text, View } from "react-native";
import searchCities from "../components/helper";

function App() {
const [input, setInput] = useState("");
const [results, setResults] = useState([]);
const handleOnChangeText = (text) => {
setInput(text);
const results = searchCities(text); // API Call made
setResults(results);
};

return (
<>
<TextInput
placeholder="Enter city name"
onChangeText={handleOnChangeText}
value={input}
style={{
width: 400,
height: 40,
borderColor: "black",
borderWidth: 1
}}
/>
{results.length > 0 && (
<View
style={{
width: 400,
borderRightWidth: 1,
borderRadius: 5
}}
>
{results.map((location, index) => {
return (
<React.Fragment key={index}>
<Text>{location}</Text>
<View
style={{
borderWidth: 0.5,
borderColor: "grey"
}}
/>
</React.Fragment>
);
})}
</View>
)}
</>
);
}

export default App;

Here, an API call is made on every time a character is entered.

Step 4: Enhancing UX with Debounced Search

The drawback of the previous method is that it triggers an API call with every user input change, including each new character and clearing. This could potentially overload the server.

To address this, there are two approaches to prevent such excessive API calls: Debouncing and Throttling.

Debouncing: When a user performs an action, such as typing in an input field, debouncing waits for a designated time period to pass after the last action before triggering the associated function or action. This helps prevent excessive function calls and improves performance.

Throttling: Throttling is similar to debouncing, but instead of waiting for a designated time after the last action, it ensures that the function is only executed at a certain rate. Throttling limits the maximum number of times a function can be called over a specific period

Let’s now enhance our implementation by incorporating Debouncing!

  useEffect(() => {
const timer = setTimeout(() => {
if (input.length !== 0) {
const results = searchCities(input); // API Call made
console.log("### Search API Call made with", input);
setResults(results);
} else if (input.length === 0) {
setResults([]);
}
}, 2000);

return () => clearTimeout(timer);
}, [input]);

Here, you’ll notice that the API call won’t trigger with every character change. Instead, it’s throttled to occur after a 2-second (2000 milliseconds) delay following the last input.

This is achieved using the useEffect hook. When the input state changes, a timer is set using setTimeout to delay the execution of the API call. If the input remains unchanged for 2 seconds, the API call is made with the current input value. If the input changes before the 2 seconds elapse, the timer is cleared, and a new timer is set.

Finally, in the cleanup phase of the useEffect, any existing timer is cleared to prevent unnecessary API calls.

This approach helps reduce the number of API calls and improves the performance of your application. It ensures that the API call is only made after a brief delay following the last input change, providing a smoother user experience.

Step 5: [Optional] Adding a Loading Indicator

You can optionally include a loader to provide visual feedback to users while search results are being fetched.

Step 6: [Optional] Optimising Performance

To optimise the performance of the progressive search feature, it’s recommended to make an API call only after a minimum of three letters are added. This helps reduce unnecessary calls and improves overall efficiency.

The final code will be as below:

import React, { useState, useEffect } from "react";
import { TextInput, Text, View } from "react-native";
import searchCities from "../components/helper";

function App() {
const [input, setInput] = useState("");
const [results, setResults] = useState([]);
const handleOnChangeText = (text) => {
setInput(text);
};

useEffect(() => {
const timer = setTimeout(() => {
if (input.length !== 0) {
const results = searchCities(input); // API Call made
console.log("### Search API Call made with", input);
setResults(results);
} else if (input.length === 0) {
setResults([]);
}
}, 2000);

return () => clearTimeout(timer);
}, [input]);

return (
<>
<TextInput
placeholder="Enter city name"
onChangeText={handleOnChangeText}
value={input}
style={{
width: 400,
height: 40,
borderColor: "black",
borderWidth: 1
}}
/>
{results.length > 0 && (
<View
style={{
width: 400,
borderRightWidth: 1,
borderRadius: 5
}}
>
{results.map((location, index) => {
return (
<React.Fragment key={index}>
<Text>{location}</Text>
<View
style={{
borderWidth: 0.5,
borderColor: "grey"
}}
/>
</React.Fragment>
);
})}
</View>
)}
</>
);
}

export default App;

For a complete working example of the above code discussed, you can explore this sandbox.

Explore the GitHub repository below for a complete implementation demonstrating how to integrate a progressive location search feature into a weather app.

--

--

Maria James
Maria James

No responses yet