In this lesson, we will create the Student List screen for our Student Management mobile application using React Native, TypeScript, Axios, FlatList, search, and pagination.
The Student List screen will communicate with the PHP REST API, retrieve student records, display them using FlatList, and provide options for searching, editing, viewing, and deleting students.
Student List Screen
↓
Axios GET Request
↓
JWT Authorization
↓
PHP Student API
↓
MySQL Database
↓
JSON Student Data
↓
TypeScript
↓
FlatList
↓
Display Students
React Native FlatList is designed to efficiently display
large lists of data.
renderItem.interface Student {
id: number;
name: string;
email: string;
mobile: string;
course: string;
address: string;
}
This interface represents one student returned by the REST API.
interface PaginationMeta {
page: number;
limit: number;
total: number;
total_pages: number;
}
interface StudentResponse {
success: boolean;
data: Student[];
meta: PaginationMeta;
}
import React, {
useEffect,
useState
} from "react";
import {
View,
Text,
TextInput,
FlatList,
ActivityIndicator,
RefreshControl,
TouchableOpacity
} from "react-native";
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState(false);
const [refreshing, setRefreshing] =
useState(false);
const [loadingMore, setLoadingMore] =
useState(false);
const [page, setPage] =
useState(1);
const [hasMore, setHasMore] =
useState(true);
const [search, setSearch] =
useState("");
const [searchLoading, setSearchLoading] =
useState(false);
The search value will be sent to the Student API as a query parameter.
GET /api/students.php
The endpoint can return students with pagination and optional search.
/students.php?page=1&limit=10
const response =
await api.get<StudentResponse>(
"/students.php",
{
params: {
page: 1,
limit: 10
}
}
);
setStudents(
response.data.data
);
const loadStudents =
async () => {
try {
setLoading(true);
const response =
await api.get<StudentResponse>(
"/students.php",
{
params: {
page: 1,
limit: 10,
search
}
}
);
setStudents(
response.data.data
);
setPage(1);
setHasMore(
response.data.meta.page
< response.data.meta.total_pages
);
} catch (error) {
console.log(
"Unable to load students"
);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadStudents();
}, []);
The API request runs when the screen is first mounted.
<TextInput
placeholder="Search students..."
value={search}
onChangeText={setSearch}
/>
The user can enter a name, email, mobile number, or course.
const searchStudents =
async (keyword: string) => {
try {
setSearchLoading(true);
const response =
await api.get<StudentResponse>(
"/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
);
} finally {
setSearchLoading(false);
}
};
interface StudentCardProps {
student: Student;
}
const StudentCard =
({
student
}: StudentCardProps) => {
return (
<View>
<Text>
{student.name}
</Text>
<Text>
{student.email}
</Text>
<Text>
{student.mobile}
</Text>
<Text>
{student.course}
</Text>
</View>
);
};
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<StudentCard
student={item}
/>
)}
/>
keyExtractor={item =>
item.id.toString()
}
Every student should have a unique key. The database ID is a good choice when it is unique.
Each student item can provide actions such as:
<TouchableOpacity>
<Text>View</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Edit</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Delete</Text>
</TouchableOpacity>
const deleteStudent =
async (studentId: number) => {
await api.delete(
`/students.php?id=${studentId}`
);
setStudents(
previous =>
previous.filter(
student =>
student.id !== studentId
)
);
};
After a successful DELETE request, the local list can immediately remove the deleted student.
const loadMore =
async () => {
if (
loadingMore ||
!hasMore
) {
return;
}
const nextPage =
page + 1;
try {
setLoadingMore(true);
const response =
await api.get<StudentResponse>(
"/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 }) => (
<StudentCard
student={item}
/>
)}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
/>
When the user reaches the end of the list, another page can be requested.
const renderFooter = () => {
if (!loadingMore) {
return null;
}
return (
<ActivityIndicator />
);
};
This indicator can be displayed at the bottom of the list while the next page is loading.
const refreshStudents =
async () => {
try {
setRefreshing(true);
const response =
await api.get<StudentResponse>(
"/students.php",
{
params: {
search,
page: 1,
limit: 10
}
}
);
setStudents(
response.data.data
);
setPage(1);
setHasMore(
response.data.meta.page
< response.data.meta.total_pages
);
} finally {
setRefreshing(false);
}
};
<FlatList
data={students}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={
refreshStudents
}
/>
}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<StudentCard
student={item}
/>
)}
/>
{loading &&
students.length === 0 && (
<ActivityIndicator />
)}
{!loading &&
students.length === 0 && (
<Text>
No students found
</Text>
)}
These states provide clear feedback when data is loading or no matching students are available.
try {
const response =
await api.get<StudentResponse>(
"/students.php"
);
setStudents(
response.data.data
);
} catch (error) {
if (
axios.isAxiosError(error) &&
error.response
) {
if (
error.response.status === 401
) {
// Token expired
} else {
// API error
}
} else {
// Network error
}
}
The Student List screen can navigate to other project screens.
Student List
|
+---- View → Student Profile
|
+---- Edit → Edit Student
|
+---- Delete → Delete API
|
+---- Add → Add Student
React Navigation can be used to connect these screens.
const StudentListScreen =
() => {
const [students, setStudents] =
useState<Student[]>([]);
const [page, setPage] =
useState(1);
const [hasMore, setHasMore] =
useState(true);
const [loading, setLoading] =
useState(false);
const loadStudents =
async () => {
try {
setLoading(true);
const response =
await api.get<StudentResponse>(
"/students.php",
{
params: {
page: 1,
limit: 10
}
}
);
setStudents(
response.data.data
);
setPage(1);
setHasMore(
response.data.meta.page
< response.data.meta.total_pages
);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadStudents();
}, []);
return (
<View>
<Text>
Students
</Text>
<FlatList
data={students}
keyExtractor={
item =>
item.id.toString()
}
renderItem={
({ item }) => (
<View>
<Text>
{item.name}
</Text>
<Text>
{item.course}
</Text>
</View>
)
}
/>
</View>
);
};
Student List Screen
↓
Search / Refresh / Scroll
↓
Axios API Service
↓
JWT Interceptor
↓
PHP Student API
↓
Authentication
↓
Authorization
↓
MySQL + PDO
↓
Paginated JSON
↓
TypeScript Interfaces
↓
FlatList
↓
Student Cards
↓
View / Edit / Delete
The Student List screen is one of the main screens of our Student Management mobile application.
onEndReached can load more records.Question: Which React Native component is commonly used to efficiently display a large list of student records?