Lists and Keys in TypeScript
Level: Beginner to Intermediate
- What Lists and Keys in TypeScript means in React + TypeScript
- How type safety improves React components
- How to model School Management System data with interfaces and types
- Common TypeScript mistakes to avoid
- How to explain this topic in interviews
Why This Matters
Lists and Keys in TypeScript helps you build React screens with fewer runtime bugs. In real .NET Web API projects, TypeScript makes API DTOs, component props, form state, and shared data contracts easier to understand and safer to change.
Type-safe list rendering with full type checking on arrays and keys.
Prerequisite: Read React JS article 08 first.
The Problem
React JavaScript can fail at runtime when props, API responses, or form values have the wrong shape. This lesson shows how Lists and Keys in TypeScript uses TypeScript to catch many of those mistakes while you write code, before the student dashboard reaches users.
Typed List Rendering
function StudentList({ students }: { students: Student[] }) {
return (
<ul>
{students.map((student: Student) => (
<li key={student.id}>{student.name}</li>
))}
</ul>
);
}
// Type inference works too
function StudentListInferred({ students }: { students: Student[] }) {
return (
<ul>
{students.map(student => (
<li key={student.id}>{student.name}</li>
))}
</ul>
);
}
Array Methods with Types
// filter returns typed array
const topStudents: Student[] = students.filter((s: Student) => s.marks >= 90);
// sort with typed comparison
const sorted: Student[] = [...students].sort((a: Student, b: Student) => {
return b.marks - a.marks;
});
// map with type transformation
const studentNames: string[] = students.map((s: Student) => s.name);
// find returns optional
const ravi: Student | undefined = students.find(
(s: Student) => s.name === 'Ravi'
);
// reduce with type accumulator
const totalMarks: number = students.reduce((sum: number, s: Student) => {
return sum + s.marks;
}, 0);
Generic List Component
interface ListProps<T extends { id: number }> {
items: T[];
renderItem: (item: T) => React.ReactNode;
onItemClick?: (item: T) => void;
}
function List<T extends { id: number }>({
items,
renderItem,
onItemClick
}: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li
key={item.id}
onClick={() => onItemClick?.(item)}
style={{ cursor: onItemClick ? 'pointer' : 'default' }}
>
{renderItem(item)}
</li>
))}
</ul>
);
}
// Usage
<List<Student>
items={students}
renderItem={s => s.name}
onItemClick={student => console.log(student.id)}
/>
<List<Teacher>
items={teachers}
renderItem={t => `${t.name} (${t.subject})`}
/>
Filtering and Sorting Lists
function StudentFiltered() {
const [className, setClassName] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'marks'>('name');
const filtered: Student[] = className
? students.filter((s: Student) => s.className === className)
: students;
const sorted: Student[] = [...filtered].sort((a: Student, b: Student) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return b.marks - a.marks;
});
return (
<div>
<select value={className} onChange={e => setClassName(e.target.value)}>
<option value="">All Classes</option>
<option value="10">Class 10</option>
<option value="11">Class 11</option>
</select>
<ul>
{sorted.map(s => (
<li key={s.id}>{s.name} - {s.marks}</li>
))}
</ul>
</div>
);
}
Nested Lists with Types
interface SchoolData {
id: number;
name: string;
students: Student[];
}
function SchoolDashboard({ schools }: { schools: SchoolData[] }) {
return (
<div>
{schools.map((school: SchoolData) => (
<div key={school.id}>
<h2>{school.name}</h2>
<ul>
{school.students.map((student: Student) => (
<li key={student.id}>{student.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
Type-Safe List Operations Hook
interface ListState<T extends { id: number }> {
items: T[];
add: (item: T) => void;
remove: (id: number) => void;
update: (id: number, item: Partial<T>) => void;
clear: () => void;
}
function useList<T extends { id: number }>(
initialItems: T[] = []
): ListState<T> {
const [items, setItems] = useState<T[]>(initialItems);
const add = (item: T): void => {
setItems(prev => [...prev, item]);
};
const remove = (id: number): void => {
setItems(prev => prev.filter(item => item.id !== id));
};
const update = (id: number, updates: Partial<T>): void => {
setItems(prev =>
prev.map(item =>
item.id === id ? { ...item, ...updates } : item
)
);
};
const clear = (): void => {
setItems([]);
};
return { items, add, remove, update, clear };
}
// Usage
function StudentManager() {
const { items, add, remove } = useList<Student>([]);
return (
<div>
<button onClick={() => add({
id: Date.now(),
name: 'New Student',
className: '10',
marks: 0
})}>
Add Student
</button>
<ul>
{items.map(s => (
<li key={s.id}>
{s.name}
<button onClick={() => remove(s.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
Empty State Handling
interface StudentListProps {
students: Student[];
isLoading?: boolean;
}
function StudentList({ students, isLoading = false }: StudentListProps) {
if (isLoading) return <p>Loading...</p>;
if (students.length === 0) {
return <p>No students found.</p>;
}
return (
<ul>
{students.map((s: Student) => (
<li key={s.id}>{s.name}</li>
))}
</ul>
);
}
Key Takeaways
- Type array items with generics
- Array methods are type-checked
- Generics for reusable list components
- Optional key must be unique number/string
- Empty states important
- Next: Conditional rendering with types
- No type on map — Items could be any type
- String index as key — Breaks on reorder
- Not handling empty — Confuses users
- Mutable operations — Always create new arrays
Constrain generics to ensure id exists:
function renderList<T extends { id: number | string }>(items: T[]) {
return items.map(item => <li key={item.id}>{/* */}</li>);
}
Use ChatGPT, Claude, or Copilot to go deeper on Typed Lists. Try these prompts:
"Why constrain generics to { id: number }?""How do you type array filter/map operations?""When would you use a generic list component?""Quiz me on typed lists"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.
Quick Definitions
- Lists and Keys in TypeScript - The main React + TypeScript concept explained in this lesson.
- Type/interface - A contract that describes the shape of data.
- Typed props/state - React data with clear compile-time expectations.
- API DTO - The request or response shape shared with ASP.NET Core Web API.
Common Mistakes
- Using
anytoo quickly instead of defining a useful type - Typing props loosely and losing the benefit of TypeScript
- Forgetting that API data can still be missing or invalid at runtime
- Making types too complex before the component is stable
- Not sharing clear DTO shapes between frontend and backend teams
Practice Task
Create a small React TypeScript example using Lists and Keys in TypeScript. Keep it connected to a School Management System scenario.
Suggested practice:
- Define a clear
Student,Teacher, orAttendancetype. - Build a small typed component or hook.
- Add one valid example and one intentionally wrong example to see TypeScript errors.
- Explain the type contract in your own words.
- Rebuild the same example once without looking at the article.
Quick Revision
| Question | Answer |
|---|---|
| What is the main idea? | Use TypeScript to make Lists and Keys in TypeScript safer in React. |
| Where is it used? | Props, state, forms, API responses, context, hooks, and routes. |
| What should beginners avoid? | Overusing any and ignoring runtime API validation. |
| What is the best debugging habit? | Read the TypeScript error, check the data shape, and fix the type contract. |
Lists and Keys in TypeScript is a React + TypeScript concept that improves safety and maintainability. I would explain which values need types, how TypeScript catches mistakes early, and how it helps when consuming ASP.NET Core Web API responses.
It is used in typed props, API response models, form state, route parameters, context values, custom hooks, and reusable UI components.