In this lesson, we will create the Student Search screen for our Student Management mobile application using React Native, TypeScript, Axios, FlatList, and the PHP REST API.
The user will enter a search keyword and the application will send that keyword to the PHP API. The server will search the MySQL database and return matching students.
Search Screen
↓
Enter Keyword
↓
Axios GET Request
↓
PHP REST API
↓
MySQL Search
↓
JSON Response
↓
TypeScript
↓
FlatList
↓
Matching Students
Server-side search is useful when the database contains a large number of student records.
interface Student {
id: number;
name: string;
email: string;
mobile: string;
course: string;
address: string;
}
interface PaginationMeta {
page: number;
limit: number;
total: number;
total_pages: number;
}
interface StudentSearchResponse {
success: boolean;
data: Student[];
meta: PaginationMeta;
}
import React, {
useEffect,
useState
} from "react";
import {
View,
Text,
TextInput,
FlatList,
ActivityIndicator,
TouchableOpacity,
RefreshControl
} from "react-native";
const [search, setSearch] =
useState("");
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState(false);
const [error, setError] =
useState("");
const [page, setPage] =
useState(1);
const [loadingMore, setLoadingMore] =
useState(false);
const [hasMore, setHasMore] =
useState(true);
Pagination allows the application to load search results page by page.
GET /api/students.php?search=rahul
The search query parameter contains the keyword entered
by the user.
const response =
await api.get<StudentSearchResponse>(
"/students.php",
{
params: {
search: search,
page: 1,
limit: 10
}
}
);
setStudents(
response.data.data
);
const searchStudents =
async (keyword: string) => {
try {
setLoading(true);
setError("");
const response =
await api.get<StudentSearchResponse>(
"/students.php",
{
params: {
search: keyword,
page: 1,
limit: 10
}
}
);
setStudents(
response.data.data
);
setPage(1);
setHasMore(
response.data.meta.page
< response.data.meta.total_pages
);
} catch (error) {
setError(
"Unable to search students."
);
} finally {
setLoading(false);
}
};
<TextInput
placeholder="Search students..."
value={search}
onChangeText={setSearch}
/>
The TextInput stores the current search keyword.
<TouchableOpacity
onPress={() =>
searchStudents(search)
}
>
<Text>
Search
</Text>
</TouchableOpacity>
const clearSearch =
async () => {
setSearch("");
await searchStudents("");
};
Clearing the search keyword can reload the normal student list.
$search =
trim($_GET['search'] ?? '');
PHP reads the search keyword from the URL query parameter.
$keyword = "%" . $search . "%";
$stmt = $pdo->prepare(
"SELECT
id,
name,
email,
mobile,
course,
address
FROM students
WHERE
name LIKE ?
OR email LIKE ?
OR mobile LIKE ?
OR course LIKE ?"
);
$stmt->execute([
$keyword,
$keyword,
$keyword,
$keyword
]);
The API can search multiple student fields using the SQL
LIKE operator.
Search keywords come from users, so they should never be directly inserted into an SQL query.
$stmt = $pdo->prepare(
"SELECT * FROM students
WHERE name LIKE ?"
);
$stmt->execute([
"%" . $search . "%"
]);
Prepared statements help prevent SQL injection.
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<View>
<Text>
{item.name}
</Text>
<Text>
{item.email}
</Text>
<Text>
{item.course}
</Text>
</View>
)}
/>
{!loading &&
students.length === 0 && (
<Text>
No students found.
</Text>
)}
An empty state clearly informs the user that no student matches the search keyword.
{loading && (
<ActivityIndicator
size="large"
/>
)}
Display a loading indicator while the search request is being processed.
try {
const response =
await api.get<StudentSearchResponse>(
"/students.php",
{
params: {
search,
page: 1,
limit: 10
}
}
);
} catch (error) {
if (
axios.isAxiosError(error)
) {
if (
error.response?.status === 401
) {
setError(
"Session expired."
);
} else {
setError(
error.response?.data?.message
|| "Search failed."
);
}
} else {
setError(
"Network error."
);
}
}
const loadMore =
async () => {
if (
loadingMore ||
!hasMore
) {
return;
}
const nextPage =
page + 1;
try {
setLoadingMore(true);
const response =
await api.get<StudentSearchResponse>(
"/students.php",
{
params: {
search,
page: nextPage,
limit: 10
}
}
);
setStudents(
previous => [
...previous,
...response.data.data
]
);
setPage(nextPage);
setHasMore(
nextPage
< response.data.meta.total_pages
);
} finally {
setLoadingMore(false);
}
};
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<Text>
{item.name}
</Text>
)}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
/>
When the user reaches the bottom of the search results, the next page can be loaded.
GET /students.php
?search=react
&page=2
&limit=10
The server can apply the search condition first and then return the requested page of matching records.
When the search keyword changes, pagination should start from page one.
setPage(1);
setHasMore(true);
searchStudents(
newKeyword
);
This prevents the application from requesting page 5 of a completely new search.
Search results can provide the same actions as the normal Student List.
navigation.navigate(
"StudentDetails",
{
id: item.id
}
);
const searchStudents =
async (keyword: string) => {
try {
setLoading(true);
setError("");
const response =
await api.get<StudentSearchResponse>(
"/students.php",
{
params: {
search:
keyword.trim(),
page: 1,
limit: 10
}
}
);
setStudents(
response.data.data
);
setPage(1);
setHasMore(
response.data.meta.page
< response.data.meta.total_pages
);
} catch (error) {
if (
axios.isAxiosError(error)
) {
setError(
error.response?.data?.message
|| "Search failed."
);
} else {
setError(
"Network error."
);
}
} finally {
setLoading(false);
}
};
const StudentSearchScreen =
({ navigation }: any) => {
const [search, setSearch] =
useState("");
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState(false);
const [error, setError] =
useState("");
const searchStudents =
async () => {
try {
setLoading(true);
setError("");
const response =
await api.get<StudentSearchResponse>(
"/students.php",
{
params: {
search:
search.trim(),
page: 1,
limit: 10
}
}
);
setStudents(
response.data.data
);
} catch (error) {
setError(
"Unable to search students."
);
} finally {
setLoading(false);
}
};
return (
<View>
<Text>
Search Students
</Text>
<TextInput
placeholder="Search by name, email, mobile or course"
value={search}
onChangeText={setSearch}
/>
<TouchableOpacity
onPress={searchStudents}
>
<Text>
Search
</Text>
</TouchableOpacity>
{loading && (
<ActivityIndicator />
)}
{error !== "" && (
<Text>
{error}
</Text>
)}
{!loading &&
students.length === 0 && (
<Text>
No students found.
</Text>
)}
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() =>
navigation.navigate(
"StudentDetails",
{
id: item.id
}
)
}
>
<Text>
{item.name}
</Text>
<Text>
{item.email}
</Text>
<Text>
{item.course}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
};
Search Screen
↓
TextInput
↓
Search Keyword
↓
Axios
↓
JWT Authorization
↓
PHP API
↓
$_GET['search']
↓
PDO Prepared Statement
↓
MySQL LIKE Search
↓
Pagination
↓
JSON Response
↓
TypeScript
↓
FlatList
↓
Student Results
↓
Details / Edit / Delete
The Student Search screen allows users to quickly find students stored in the MySQL database through the REST API.
Question: Which SQL operator is commonly used for keyword searching in our Student API?