React Native's FlatList is commonly used to display lists
received from REST APIs. When using TypeScript, we can define the API
data structure and use that type throughout the FlatList component.
FlatList is a React Native component used to efficiently
display a list of data.
<FlatList
data={students}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
/>
It is especially useful for API data containing many records.
interface Student {
id: number;
name: string;
email: string;
mobile: string;
}
This interface describes one student object.
const students: Student[] = [];
Student[] means an array containing Student objects.
const students: Student[] = [
{
id: 1,
name: "Rahul",
email: "rahul@example.com",
mobile: "9876543210"
}
];
React Native state can be typed using the Student interface.
const [students, setStudents] =
useState<Student[]>([]);
TypeScript now knows that students contains an array of Student objects.
interface StudentResponse {
success: boolean;
message: string;
data: Student[];
}
This represents a typical PHP REST API response containing multiple students.
const response =
await api.get<StudentResponse>(
"/students"
);
setStudents(response.data.data);
Axios knows that the API response follows the
StudentResponse interface.
<FlatList
data={students}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
/>
Because students is typed as Student[],
TypeScript understands the type of item.
FlatList requires a stable key for each list item.
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
/>
renderItem={({ item }) => (
<View>
<Text>{item.name}</Text>
<Text>{item.email}</Text>
<Text>{item.mobile}</Text>
</View>
)}
TypeScript provides information about the available Student properties.
A separate component can be used to display each student.
interface StudentCardProps {
student: Student;
}
const StudentCard = ({
student
}: StudentCardProps) => {
return (
<View>
<Text>{student.name}</Text>
<Text>{student.email}</Text>
</View>
);
};
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<StudentCard student={item} />
)}
/>
API data can be loaded using useEffect().
useEffect(() => {
const loadStudents = async () => {
const response =
await api.get<StudentResponse>(
"/students"
);
setStudents(response.data.data);
};
loadStudents();
}, []);
const [loading, setLoading] =
useState<boolean>(false);
The loading state can be used while the API request is running.
setLoading(true);
try {
// API request
} finally {
setLoading(false);
}
const [errorMessage, setErrorMessage] =
useState<string>("");
If the API request fails, a message can be stored in this state.
setErrorMessage(
"Unable to load students"
);
{loading && (
<Text>Loading...</Text>
)}
{errorMessage !== "" && (
<Text>{errorMessage}</Text>
)}
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
/>
FlatList provides ListEmptyComponent for empty data.
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
ListEmptyComponent={
<Text>No students found</Text>
}
/>
FlatList can refresh API data using RefreshControl or
its refresh properties.
<FlatList
data={students}
refreshing={refreshing}
onRefresh={loadStudents}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
/>
interface StudentSearchParams {
search: string;
}
const params: StudentSearchParams = {
search: "Rahul"
};
const response =
await api.get<StudentResponse>(
"/students",
{
params
}
);
setStudents(response.data.data);
interface PaginationMeta {
page: number;
limit: number;
total: number;
totalPages: number;
}
interface StudentResponse {
success: boolean;
message: string;
data: Student[];
meta: PaginationMeta;
}
The FlatList can use this metadata to implement pagination.
The onEndReached property can load another API page
when the user reaches the end of the list.
<FlatList
data={students}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<Text>{item.name}</Text>
)}
/>
const [page, setPage] =
useState<number>(1);
const [hasMore, setHasMore] =
useState<boolean>(true);
const [loadingMore, setLoadingMore] =
useState<boolean>(false);
TypeScript keeps the pagination state consistent.
When another page is loaded, the new students can be appended.
setStudents(previous => [
...previous,
...response.data.data
]);
Both arrays are typed as Student arrays.
interface StudentItemProps {
item: Student;
}
const StudentItem = ({
item
}: StudentItemProps) => {
return (
<View>
<Text>{item.name}</Text>
<Text>{item.email}</Text>
<Text>{item.mobile}</Text>
</View>
);
};
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<StudentItem item={item} />
)}
/>
The StudentItem component receives only a Student object.
any[] instead of a specific interface.interface Student {
id: number;
name: string;
email: string;
mobile: string;
}
interface PaginationMeta {
page: number;
limit: number;
total: number;
totalPages: number;
}
interface StudentResponse {
success: boolean;
message: string;
data: Student[];
meta: PaginationMeta;
}
const loadStudents = async () => {
try {
setLoading(true);
setErrorMessage("");
const response =
await api.get<StudentResponse>(
"/students?page=1&limit=10"
);
setStudents(response.data.data);
} catch (error) {
setErrorMessage(
"Unable to load students"
);
} finally {
setLoading(false);
}
};
import React, { useEffect, useState } from "react";
import {
View,
Text,
FlatList
} from "react-native";
import api from "./api";
interface Student {
id: number;
name: string;
email: string;
mobile: string;
}
interface StudentResponse {
success: boolean;
message: string;
data: Student[];
}
const StudentList = () => {
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState<boolean>(false);
const [errorMessage, setErrorMessage] =
useState<string>("");
const loadStudents = async () => {
try {
setLoading(true);
setErrorMessage("");
const response =
await api.get<StudentResponse>(
"/students"
);
setStudents(response.data.data);
} catch (error) {
setErrorMessage(
"Unable to load students"
);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadStudents();
}, []);
return (
<View>
{loading && (
<Text>Loading...</Text>
)}
{errorMessage !== "" && (
<Text>{errorMessage}</Text>
)}
<FlatList
data={students}
keyExtractor={item =>
item.id.toString()
}
renderItem={({ item }) => (
<View>
<Text>{item.name}</Text>
<Text>{item.email}</Text>
<Text>{item.mobile}</Text>
</View>
)}
ListEmptyComponent={
<Text>No students found</Text>
}
/>
</View>
);
};
export default StudentList;
PHP + MySQL
↓
REST API
↓
JSON Response
↓
Axios
↓
StudentResponse Interface
↓
React Native State
↓
Student[]
↓
FlatList
↓
Typed Student Item
↓
Mobile UI
The PHP REST API returns student data as JSON. Axios receives the response, the TypeScript interface describes its structure, and the Student array is stored in React Native state.
Finally, FlatList displays each Student object. This approach gives us a clean and type-safe connection between the REST API and the React Native user interface.
Student[] for an array of student objects.renderItem receives a typed item.keyExtractor with a unique ID.Question: Which property of FlatList contains the array of records that should be displayed?