In this lesson, we will build a small React Native application using TypeScript. The project will combine the concepts learned in the previous lessons, including components, interfaces, useState, forms, API-style data, navigation concepts, reusable components, and typed functions.
The goal of this project is to understand how different TypeScript concepts work together inside a real React Native application.
Our mini project will contain a student management screen.
StudentApp/
src/
components/
StudentCard.tsx
StudentForm.tsx
screens/
StudentScreen.tsx
types/
student.ts
App.tsx
export interface Student {
id: number;
name: string;
age: number;
course: string;
}
This interface describes the structure of a student object.
const initialStudents: Student[] = [
{
id: 1,
name: "Rahul",
age: 20,
course: "React Native"
},
{
id: 2,
name: "Priya",
age: 21,
course: "TypeScript"
},
{
id: 3,
name: "Amit",
age: 19,
course: "JavaScript"
}
];
const [students, setStudents] =
useState<Student[]>(
initialStudents
);
TypeScript knows that the state contains an array of Student objects.
const [search, setSearch] =
useState<string>("");
The search state stores the text entered by the user.
interface StudentCardProps {
student: Student;
}
function StudentCard(
{ student }: StudentCardProps
): React.JSX.Element {
return (
<View>
<Text>
{student.name}
</Text>
<Text>
{student.course}
</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
padding: 15,
marginBottom: 10,
borderWidth: 1,
borderRadius: 8
},
name: {
fontSize: 18,
fontWeight: "bold"
}
});
<FlatList<Student>
data={students}
keyExtractor={(item) =>
item.id.toString()
}
renderItem={({ item }) => (
<StudentCard
student={item}
/>
)}
/>
const filteredStudents =
students.filter(
(student: Student) =>
student.name
.toLowerCase()
.includes(
search.toLowerCase()
)
);
<TextInput
placeholder="Search student"
value={search}
onChangeText={(
text: string
) => setSearch(text)}
style={styles.input}
/>
interface StudentForm {
name: string;
age: string;
course: string;
}
const [form, setForm] =
useState<StudentForm>({
name: "",
age: "",
course: ""
});
const updateField = (
field: keyof StudentForm,
value: string
): void => {
setForm(
previous => ({
...previous,
[field]: value
})
);
};
The keyof operator ensures that only valid form field
names can be passed to the function.
interface InputProps {
placeholder: string;
value: string;
onChangeText:
(text: string) => void;
}
function AppInput(
{
placeholder,
value,
onChangeText
}: InputProps
): React.JSX.Element {
return (
<TextInput
placeholder={placeholder}
value={value}
onChangeText={onChangeText}
style={styles.input}
/>
);
}
const addStudent = (): void => {
const newStudent: Student = {
id: Date.now(),
name: form.name,
age: Number(form.age),
course: form.course
};
setStudents(
previous => [
...previous,
newStudent
]
);
};
const validateForm = (): boolean => {
if (
!form.name ||
!form.age ||
!form.course
) {
return false;
}
return true;
};
Validation can prevent incomplete student records from being added.
const addStudent = (): void => {
if (!validateForm()) {
Alert.alert(
"Error",
"Please fill all fields"
);
return;
}
const newStudent: Student = {
id: Date.now(),
name: form.name,
age: Number(form.age),
course: form.course
};
setStudents(
previous => [
...previous,
newStudent
]
);
};
const clearForm = (): void => {
setForm({
name: "",
age: "",
course: ""
});
};
const deleteStudent = (
id: number
): void => {
setStudents(
previous =>
previous.filter(
student =>
student.id !== id
)
);
};
The function accepts only a number because student IDs are numbers.
interface StudentCardProps {
student: Student;
onDelete:
(id: number) => void;
}
Function props should be typed so that the child component knows how the function should be called.
function StudentCard(
{
student,
onDelete
}: StudentCardProps
): React.JSX.Element {
return (
<View style={styles.card}>
<Text style={styles.name}>
{student.name}
</Text>
<Text>
Age: {student.age}
</Text>
<Text>
Course: {student.course}
</Text>
<Button
title="Delete"
onPress={() =>
onDelete(student.id)
}
/>
</View>
);
}
<FlatList<Student>
data={filteredStudents}
keyExtractor={(item) =>
item.id.toString()
}
renderItem={({ item }) => (
<StudentCard
student={item}
onDelete={
deleteStudent
}
/>
)}
/>
import React, {
useState
} from "react";
import {
View,
Text,
TextInput,
Button,
FlatList,
Alert,
StyleSheet
} from "react-native";
interface Student {
id: number;
name: string;
age: number;
course: string;
}
interface StudentForm {
name: string;
age: string;
course: string;
}
const initialStudents: Student[] = [
{
id: 1,
name: "Rahul",
age: 20,
course: "React Native"
},
{
id: 2,
name: "Priya",
age: 21,
course: "TypeScript"
}
];
export default function StudentScreen():
React.JSX.Element {
const [students, setStudents] =
useState<Student[]>(
initialStudents
);
const [search, setSearch] =
useState<string>("");
const [form, setForm] =
useState<StudentForm>({
name: "",
age: "",
course: ""
});
const updateField = (
field: keyof StudentForm,
value: string
): void => {
setForm(
previous => ({
...previous,
[field]: value
})
);
};
const addStudent = (): void => {
if (
!form.name ||
!form.age ||
!form.course
) {
Alert.alert(
"Error",
"Please fill all fields"
);
return;
}
const newStudent: Student = {
id: Date.now(),
name: form.name,
age: Number(form.age),
course: form.course
};
setStudents(
previous => [
...previous,
newStudent
]
);
setForm({
name: "",
age: "",
course: ""
});
};
const deleteStudent = (
id: number
): void => {
setStudents(
previous =>
previous.filter(
student =>
student.id !== id
)
);
};
const filteredStudents =
students.filter(
student =>
student.name
.toLowerCase()
.includes(
search.toLowerCase()
)
);
return (
<View style={styles.container}>
<Text style={styles.title}>
Student Management
</Text>
<TextInput
placeholder="Search student"
value={search}
onChangeText={(
text: string
) => setSearch(text)}
style={styles.input}
/>
<TextInput
placeholder="Student Name"
value={form.name}
onChangeText={(
text: string
) =>
updateField(
"name",
text
)
}
style={styles.input}
/>
<TextInput
placeholder="Age"
value={form.age}
keyboardType="numeric"
onChangeText={(
text: string
) =>
updateField(
"age",
text
)
}
style={styles.input}
/>
<TextInput
placeholder="Course"
value={form.course}
onChangeText={(
text: string
) =>
updateField(
"course",
text
)
}
style={styles.input}
/>
<Button
title="Add Student"
onPress={addStudent}
/>
<FlatList<Student>
data={filteredStudents}
keyExtractor={(item) =>
item.id.toString()
}
renderItem={({
item
}) => (
<View
style={styles.card}
>
<Text
style={styles.name}
>
{item.name}
</Text>
<Text>
Age: {item.age}
</Text>
<Text>
Course:
{item.course}
</Text>
<Button
title="Delete"
onPress={() =>
deleteStudent(
item.id
)
}
/>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 15
},
input: {
borderWidth: 1,
borderRadius: 8,
padding: 10,
marginBottom: 10
},
card: {
padding: 15,
marginTop: 10,
borderWidth: 1,
borderRadius: 8
},
name: {
fontSize: 18,
fontWeight: "bold"
}
});
import React from "react";
import StudentScreen
from "./src/screens/StudentScreen";
export default function App():
React.JSX.Element {
return (
<StudentScreen />
);
}
The StudentScreen becomes the main screen of the application.
keyofThis mini project can be expanded into a complete student management application.
any everywhere.In this mini project, we combined many TypeScript and React Native concepts to create a small Student Management application.
We created interfaces for students and forms, typed state, reusable components, search functionality, form validation, add and delete operations, and a FlatList for displaying student records.
This project provides a foundation for larger React Native applications where APIs, navigation, authentication, Context API, Redux Toolkit, and persistent storage can be added.
useState.FlatList<Student> for typed student data.keyof for safe form field updates.Question: Which TypeScript interface is used to describe the structure of a student object in this project?