React Native applications often communicate with APIs to load user data, products, students, posts, and other information. With TypeScript, we can define the structure of API responses and use that data more safely.
An API response is the data returned by a server after an application sends a request.
For example, an API might return:
{
"id": 101,
"name": "Rahul",
"course": "React Native"
}
API data is often used throughout an application. If the expected structure is not clearly defined, incorrect property names or types can cause problems.
TypeScript allows us to describe the expected structure of the response.
An interface can describe a single student returned by an API.
interface Student {
id: number;
name: string;
course: string;
}
{
"id": 101,
"name": "Rahul",
"course": "React Native"
}
According to our interface, id should be a number,
while name and course should be strings.
The JavaScript fetch() function can be used to request
data from an API.
const response =
await fetch("https://example.com/api/students");
The response can then be converted into JSON.
const response =
await fetch("https://example.com/api/students");
const data =
await response.json();
By default, TypeScript does not automatically know the exact structure of the JSON returned by the server.
Data received from an external API should be treated carefully. The server response may not always match the structure we expect.
A type or interface describes what our application expects, but it does not automatically validate the server response at runtime.
const getStudent = async (): Promise<Student> => {
const response =
await fetch("https://example.com/api/student");
const data =
await response.json();
return data;
};
The return type says that the function is expected to return a Student.
API functions are usually asynchronous, so their return type is commonly
written using Promise.
async function getStudent():
Promise<Student> {
// API request
}
This means the function eventually resolves to a Student value.
APIs often return multiple records.
interface Student {
id: number;
name: string;
}
const getStudents =
async (): Promise<Student[]> => {
// API request
};
Student[] means an array of Student objects.
[
{
"id": 101,
"name": "Rahul"
},
{
"id": 102,
"name": "Priya"
}
]
This response can be represented as Student[].
const [students, setStudents] =
useState<Student[]>([]);
TypeScript now knows that students contains an array of
Student objects.
Sometimes API data is not available when the component first renders.
const [student, setStudent] =
useState<Student | null>(null);
The state can contain either a Student or null.
const [loading, setLoading] =
useState<boolean>(false);
A boolean loading state can be used to display a loading indicator while API data is being requested.
const [error, setError] =
useState<string | null>(null);
The error state can contain an error message or null when there is no error.
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState<boolean>(false);
const [error, setError] =
useState<string | null>(null);
These three states are commonly used when loading API data.
const loadStudents = async (): Promise<void> => {
try {
setLoading(true);
setError(null);
const response =
await fetch(
"https://example.com/api/students"
);
const data: Student[] =
await response.json();
setStudents(data);
} catch (error) {
setError("Unable to load students");
} finally {
setLoading(false);
}
};
It is useful to check whether the HTTP request was successful before processing the response.
const response =
await fetch(url);
if (!response.ok) {
throw new Error("Request failed");
}
This allows failed HTTP requests to be handled by the error logic.
Some APIs return an object containing an array of records.
interface Student {
id: number;
name: string;
}
interface StudentResponse {
success: boolean;
students: Student[];
}
const data: StudentResponse =
await response.json();
if (data.success) {
setStudents(data.students);
}
The interface describes both the response status and the student list.
Some API properties may not always be present.
interface User {
id: number;
name: string;
phone?: string;
}
The phone property is optional.
<Text>
Phone: {user.phone ?? "Not available"}
</Text>
The nullish coalescing operator provides a fallback when the value is null or undefined.
{students.map((student: Student) => (
<View key={student.id}>
<Text>
{student.name}
</Text>
<Text>
{student.course}
</Text>
</View>
))}
TypeScript knows the properties available on each Student object.
TypeScript types are compile-time descriptions. They do not automatically validate unknown JSON received from a server at runtime.
For applications that require runtime validation, a validation library or custom validation logic can be used.
any for all API responses.Promise<Type> for typed async functions.Type[].API logic can be placed inside a reusable function or custom hook.
const useStudents = () => {
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState<boolean>(false);
const [error, setError] =
useState<string | null>(null);
return {
students,
loading,
error
};
};
import React, {
useEffect,
useState
} from "react";
import {
View,
Text,
ActivityIndicator
} from "react-native";
interface Student {
id: number;
name: string;
course: string;
}
interface StudentResponse {
success: boolean;
students: Student[];
}
export default function App(): React.JSX.Element {
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState<boolean>(true);
const [error, setError] =
useState<string | null>(null);
const loadStudents =
async (): Promise<void> => {
try {
setLoading(true);
setError(null);
const response =
await fetch(
"https://example.com/api/students"
);
if (!response.ok) {
throw new Error("Request failed");
}
const data: StudentResponse =
await response.json();
setStudents(data.students);
} catch (error) {
setError(
"Unable to load student data"
);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadStudents();
}, []);
if (loading) {
return (
<View>
<ActivityIndicator />
</View>
);
}
if (error) {
return (
<View>
<Text>{error}</Text>
</View>
);
}
return (
<View>
<Text>
Student List
</Text>
{students.map(
(student: Student) => (
<View key={student.id}>
<Text>
{student.name}
</Text>
<Text>
{student.course}
</Text>
</View>
))}
</View>
);
}
Typed API responses make it easier to understand and safely use data throughout a React Native application.
API responses are an important part of React Native applications. TypeScript allows us to describe API data using interfaces and types, then use those types with fetch functions, state, and UI components.
You should now understand typed API objects, arrays, nested responses, loading and error states, optional properties, Promise return types, and displaying API data safely in React Native.
Promise<Type> for typed asynchronous API functions.Type[] for arrays of API objects.Question: Which TypeScript type represents an array of Student objects?