Skip to main content

04. Props in TypeScript

Level: Beginner to Intermediate

ℹ️ What You'll Learn
  • Typing component props with interfaces
  • Optional vs required props
  • Function prop types (callbacks)
  • Generic props for reusable components
  • Spreading props with TypeScript
  • Common SMS prop patterns

The Problem

Props connect components. When passing the wrong type of prop, JavaScript silently fails at runtime. TypeScript catches it immediately. A callback function expecting a Student ID but getting a Student object? TypeScript errors before you even run the code.

TypeScript makes prop passing safer and self-documenting.

Basic Typed Props

interface StudentCardProps {
student: Student;
onDelete: (studentId: number) => void;
}

function StudentCard({ student, onDelete }: StudentCardProps) {
return (
<div>
<h3>{student.name}</h3>
<button onClick={() => onDelete(student.id)}>Delete</button>
</div>
);
}

// ✓ Correct — TypeScript checks types
<StudentCard student={ravi} onDelete={handleDelete} />

// ❌ Error — onDelete must be a function
// <StudentCard student={ravi} onDelete="delete" />

// ❌ Error — missing onDelete prop
// <StudentCard student={ravi} />

Union Types for Props

type StudentStatusBadgeProps = {
status: 'Active' | 'Inactive' | 'Graduated';
};

function StudentStatusBadge({ status }: StudentStatusBadgeProps) {
const colors = {
'Active': 'green',
'Inactive': 'red',
'Graduated': 'blue'
};

return <span style={{ color: colors[status] }}>{status}</span>;
}

// ✓ OK
<StudentStatusBadge status="Active" />

// ❌ Error: "Other" is not allowed
// <StudentStatusBadge status="Other" />

Generic Props

interface ListProps<T> {
items: T[];
onItemClick: (item: T) => void;
renderItem: (item: T) => React.ReactNode;
}

function List<T extends { id: number }>({ items, onItemClick, renderItem }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onItemClick(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}

// Usage with Students
<List<Student>
items={students}
onItemClick={handleStudentClick}
renderItem={(student) => student.name}
/>

Extending Props

interface BaseProps {
className?: string;
id?: string;
testId?: string;
}

interface StudentCardProps extends BaseProps {
student: Student;
onDelete?: (id: number) => void;
}

function StudentCard({
student,
onDelete,
className,
id,
testId
}: StudentCardProps) {
return (
<div className={className} id={id} data-testid={testId}>
{student.name}
{onDelete && <button onClick={() => onDelete(student.id)}>Delete</button>}
</div>
);
}

Spread Operator with Types

interface StudentProps {
name: string;
className: string;
marks: number;
}

function StudentCard(props: StudentProps) {
return (
<div>
<h3>{props.name}</h3>
<StudentDetails {...props} />
</div>
);
}

function StudentDetails({ name, className, marks }: StudentProps) {
return (
<div>
<p>{name} - Class {className}</p>
<p>Marks: {marks}</p>
</div>
);
}

Conditional Props

type StudentCardProps = {
student: Student;
} & (
| { variant: 'compact' }
| { variant: 'detailed'; onEdit?: () => void }
);

function StudentCard(props: StudentCardProps) {
if (props.variant === 'compact') {
return <div>{props.student.name}</div>;
}

return (
<div>
<h3>{props.student.name}</h3>
<p>Class: {props.student.className}</p>
{props.onEdit && <button onClick={props.onEdit}>Edit</button>}
</div>
);
}

Pick Props from Another Component

interface StudentListProps {
students: Student[];
onStudentDelete: (id: number) => void;
}

interface StudentItemProps extends Pick<StudentListProps, 'onStudentDelete'> {
student: Student;
}

function StudentList({ students, onStudentDelete }: StudentListProps) {
return students.map(s => (
<StudentItem key={s.id} student={s} onStudentDelete={onStudentDelete} />
));
}

function StudentItem({ student, onStudentDelete }: StudentItemProps) {
return (
<div>
{student.name}
<button onClick={() => onStudentDelete(student.id)}>Delete</button>
</div>
);
}

Required and Partial Props

interface AllProps {
name: string;
email: string;
phone?: string;
address?: string;
}

// Make all required
type RequiredProps = Required<AllProps>;

// Make all optional
type PartialProps = Partial<AllProps>;

// Make specific required
type WithEmail = AllProps & Required<Pick<AllProps, 'email' | 'phone'>>;

function StudentForm(props: WithEmail) {
return (
<form>
<input value={props.email} />
<input value={props.phone} />
</form>
);
}

Key Takeaways

  • Type all props with interfaces
  • Catch prop errors at write time
  • Generic props for reusability
  • Extend props to reduce duplication
  • Union types for variant components
  • Next: State in TypeScript
⚠️ Props Mistakes
  1. Not typing props — Lose type safety benefits
  2. Using any for props — Defeats purpose
  3. Prop interface too complex — Keep readable
  4. Missing optional mark — Props become required
💡 Document Props
/**
* Displays a student card with optional delete action
* @param student - The student object to display
* @param onDelete - Optional callback when delete is clicked
*/
interface StudentCardProps {
student: Student;
onDelete?: (id: number) => void;
}
🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on Typed Props. Try these prompts:

  • "How do you type event handler props?"
  • "When would you use generic props?"
  • "What's the difference between optional and required props?"
  • "Quiz me on TypeScript props"

💡 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

  • Props 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 any too 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 Props in TypeScript. Keep it connected to a School Management System scenario.

Suggested practice:

  1. Define a clear Student, Teacher, or Attendance type.
  2. Build a small typed component or hook.
  3. Add one valid example and one intentionally wrong example to see TypeScript errors.
  4. Explain the type contract in your own words.
  5. Rebuild the same example once without looking at the article.

Quick Revision

QuestionAnswer
What is the main idea?Use TypeScript to make Props 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.
🎯 How would you explain Props in TypeScript in an interview?

Props 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.

🎯 Where is this used in a real React TypeScript project?

It is used in typed props, API response models, form state, route parameters, context values, custom hooks, and reusable UI components.

Next Article

State and useState in TypeScript ->

nexcoding.in