Props Basics
Passing data from a parent component to a child - the thing that makes components reusable.
What you will be able to do
- Explain what props are, and how a component receives them
- Pass strings, numbers, booleans, arrays, objects, functions, and JSX as props
- Tell "192" from {192} - and see what goes wrong when you mix them up
- Type props with TypeScript, and read the errors it gives
- Explain one-way data flow, and why a child must never change its props
- Choose between passing a whole object and passing individual fields
The idea, in plain English
The components in Lesson 11 always showed the same thing: every CourseCard said React. Props fix that. A prop is a value a parent passes to a child, written like an HTML attribute: <CourseCard title="React" />. React collects every attribute into one object - { title: "React" } - and passes it to your component function as its first argument.
So a component with props is just a function with a parameter. The same CourseCard, called with a different title, shows a different course. One component, any number of results.
Props flow one way: from parent to child. The parent owns the data and decides what to pass; the child reads it and renders. A child never changes its props - in development React freezes the props object, and trying to write to it crashes the component.
Worked example: One CourseCard, three different courses.
Passing and receiving
In the parent, props look like attributes: <CourseCard title="React" lessons={192} />. In the child, they arrive as one object - the first parameter of the function. function CourseCard(props) receives { title: "React", lessons: 192 }, and props.title is "React".
Log the props and that is exactly what you see: {"title":"React","lessons":192}. key never appears there - React keeps it for itself (Lesson 9).
Quotes pass strings, braces pass anything
A value in quotes is always a string: title="React". A value in braces is a JavaScript expression, so it can be a number, a boolean, an array, an object, a function, JSX, or a calculation: lessons={192}, lessons={100 + 92}, title={course.name}.
lessons="192" and lessons={192} look almost the same and are not. The first passes the string "192", the second the number 192. Add 1 in the child and you get "1921" from the first and 193 from the second. With the prop typed as number, TypeScript refuses the string: "Type string is not assignable to type number".
A prop written with no value is true: <CourseCard published /> is the same as published={true}. Leave it off and the prop is undefined, which behaves like false in a condition.
Stringtitle="React" - quotes, or title={"React"}Numberlessons={192} - braces; "192" would be a stringBooleanpublished={true}, or just publishedArraytopics={["JSX", "Props", "State"]}Objectcourse={{ title: "React", lessons: 192 }} - outer braces for JSX, inner for the objectFunctiononStart={handleStart} - the function itself, not handleStart()JSXaction={<button>Start</button>}Typing props with TypeScript
Describe the props object with a type, and annotate the parameter: type CourseCardProps = { title: string; lessons: number }; function CourseCard(props: CourseCardProps). Most code destructures in the same place - function CourseCard({ title, lessons }: CourseCardProps) - which Lesson 13 covers in depth.
Untyped props do not compile in the Vite TypeScript template. function CourseCard(props) fails with "Parameter props implicitly has an any type", because strict mode is on. Every component in this course types its props.
The payoff is at the call site. Forget a required prop, misspell one, or pass the wrong kind of value, and TypeScript underlines the JSX before you ever run it.
Props make one component do the work of many
Without props you would need ReactCourseCard, TypeScriptCourseCard, and NodeCourseCard - three copies of the same markup. With a title prop, one CourseCard covers all three, and every future course too.
Combined with map from Lesson 9, props are how real lists are built: courses.map((course) => <CourseCard key={course.id} course={course} />). The list owns the data; each card receives one item.
A whole object, or individual fields?
<CourseCard course={course} /> passes the whole object. <CourseCard title={course.title} lessons={course.lessons} /> passes only the fields the card uses. Both are normal.
Pass the object when the component represents that thing - a CourseCard shows a course. Pass fields when the component is more general - a Stat that shows any label and number should not need to know what a course is.
Props are read-only
The parent owns the data; the child only reads it. This is one-way data flow, and it is what makes React apps predictable: to find out why a card shows "React", you look up the tree at whoever passed it.
React enforces it in development. props.title = "Angular" throws "Cannot assign to read only property title", and the component fails to render. In a production build the same line runs silently - which is worse, because the bug only shows up in the build you did not test.
Reassigning a destructured variable - title = "Angular" - does not touch the props object, so nothing throws, but it is still a sign of a design problem. If a value needs to change in response to the user, that is state - Module 3.
Watch out: Writing to props crashes in development and silently works in production. Never write to props - not even when the production build seems fine.
Functions and JSX as props
A prop can hold a function: <CourseCard onStart={handleStart} />. The child calls it - usually from an event - and the parent code runs. That is how a child tells its parent something happened, and it is the whole of Lesson 14.
A prop can also hold JSX, typed as ReactNode: <CourseCard action={<button>Start</button>} /> lets the parent decide what goes in a slot inside the card. The special children prop, for content between the opening and closing tags, is Lesson 15.
Syntax and examples
type CourseCardProps = {
title: string;
};
function CourseCard(props: CourseCardProps) {
return <h2>{props.title}</h2>;
}
function App() {
return (
<div>
<CourseCard title="React" />
<CourseCard title="TypeScript" />
<CourseCard title="Node.js" />
</div>
);
}
// React
// TypeScript
// Node.js<CourseCard key={1} title="React" lessons={192} published />
function CourseCard(props: CourseCardProps) {
console.log(props);
// { title: "React", lessons: 192, published: true } - no key
return <h2>{props.title}</h2>;
}function Stats({ lessons }: { lessons: number }) {
return <p>{lessons + 1}</p>;
}
<Stats lessons={192} />; // 193
<Stats lessons="192" />; // TypeScript: Type 'string' is not assignable to type 'number'.
// Without types, it renders "1921" - string + 1 joins them.import type { ReactNode } from "react";
type CourseCardProps = {
title: string;
lessons: number;
published: boolean;
topics: string[];
instructor: { name: string; experience: number };
onStart: () => void;
action: ReactNode;
};
function CourseCard({ title, lessons, published, topics, instructor, onStart, action }: CourseCardProps) {
return (
<article>
<h2>{title}</h2>
<p>{lessons} lessons</p>
{published && <span>Published</span>}
<ul>
{topics.map((topic) => (
<li key={topic}>{topic}</li>
))}
</ul>
<p>Instructor: {instructor.name}, {instructor.experience} years</p>
<button onClick={onStart}>Start Learning</button>
{action}
</article>
);
}
<CourseCard
title="React"
lessons={192}
published
topics={["JSX", "Props", "State", "Hooks"]}
instructor={{ name: "Chandu", experience: 8 }}
onStart={() => console.log("Started")}
action={<a href="/resources/react">Syllabus</a>}
/>;type Course = {
id: number;
title: string;
level: string;
lessons: number;
};
const courses: Course[] = [
{ id: 1, title: "React", level: "Beginner", lessons: 192 },
{ id: 2, title: "TypeScript", level: "Intermediate", lessons: 133 },
{ id: 3, title: "Node.js", level: "Intermediate", lessons: 120 },
];
function CourseCard({ course }: { course: Course }) {
return (
<article>
<h2>{course.title}</h2>
<p>Level: {course.level}</p>
<p>Lessons: {course.lessons}</p>
</article>
);
}
function CourseList() {
return (
<div>
{courses.map((course) => (
<CourseCard key={course.id} course={course} />
))}
</div>
);
}function CoursePage() {
const courseName = "React"; // owned here
return <CourseCard title={courseName} />;
}
function CourseCard({ title }: { title: string }) {
const message = "Start Learning"; // the card's own local value
return (
<div>
{/* title comes from the parent */}
<h2>{title}</h2>
{/* message is the card's own */}
<button>{message}</button>
</div>
);
}function CourseCard(props: { title: string }) {
props.title = "Angular";
return <h2>{props.title}</h2>;
}
// Development: TypeError: Cannot assign to read only property 'title' of object
// - the component fails to render.
// Production: renders "Angular" silently.type ButtonProps = {
label: string;
disabled: boolean;
};
function Button({ label, disabled }: ButtonProps) {
return <button disabled={disabled}>{label}</button>;
}
<Button label="Start Course" disabled={false} />;
<Button label="Coming Soon" disabled={true} />;Tip: Read a props type like a contract. It lists what the component needs to do its job, and TypeScript holds every caller to it.
Props at a glance
PassAttributes on the component tag.
<CourseCard title="React" lessons={192} />ReceiveOne object, the first parameter.
function CourseCard(props: Props)
TypeDescribe the object with a type.
type Props = { title: string };String vs numberQuotes pass a string; braces pass the value.
lessons={192}true shorthandA bare prop is true.
<CourseCard published />
Read-onlyNever assign to props.
// props.title = ... throws in dev
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“Add console.log(props) to a component, pass it three props and a key, and read what arrives.”
“Render {lessons + 1} in a component, then pass lessons="192" and lessons={192} (with the type removed). What do you get each time?”
“With a typed CourseCard, leave out a required prop, misspell another, and pass a string for a number. Read each TypeScript error.”
“Set props.title = "Angular" inside a component and load the page in development. Then run a production build and preview it.”
What usually goes wrong
Quotes always pass a string, so arithmetic joins instead of adding.
✗ <CourseCard lessons="192" />✓ <CourseCard lessons={192} />Strict mode rejects an implicit any parameter.
✗ function CourseCard(props) { ... }✓ function CourseCard(props: CourseCardProps) { ... }Props belong to the parent. React throws in development and silently allows it in production.
✗ props.title = "New Title";✓ // the parent passes a different title - or the value becomes statehandleStart() runs during render and passes its return value. TypeScript reports "Type void is not assignable to type () => void".
✗ <CourseCard onStart={handleStart()} />✓ <CourseCard onStart={handleStart} />An object prop needs two sets of braces: the outer pair switches to JavaScript, the inner pair is the object.
✗ course={ title: "React" }✓ course={{ title: "React" }}Best practices
- Type every component props with a named type, such as CourseCardProps.
- Use quotes only for fixed strings; everything else goes in braces.
- Pass the whole object when the component represents it, and fields when it is more general.
- Treat props as read-only, always.
- Keep the data in the parent and let children render it.
Practice
Write these yourself before opening anything. Getting them wrong first is most of how this sticks.
Build a LessonCard with title: string, duration: number, and completed: boolean that shows the title, "Duration: 30 minutes", and either "✓ Completed" or "○ Not Completed".
Show hintHide hint
Two outcomes for completed - a ternary.
Show solutionHide solution
type LessonCardProps = {
title: string;
duration: number;
completed: boolean;
};
function LessonCard({ title, duration, completed }: LessonCardProps) {
return (
<article>
<h3>{title}</h3>
<p>Duration: {duration} minutes</p>
<p>{completed ? "✓ Completed" : "○ Not Completed"}</p>
</article>
);
}
<LessonCard title="JSX Fundamentals" duration={30} completed />;
<LessonCard title="Props Basics" duration={25} completed={false} />;Build a CourseCard with title, instructor, lessons, level, rating, and enrolledStudents, showing enrolled students with thousands separators (12,450). Render three courses with it.
Show hintHide hint
toLocaleString formats a number for display.
Show solutionHide solution
type CourseCardProps = {
title: string;
instructor: string;
lessons: number;
level: string;
rating: number;
enrolledStudents: number;
};
function CourseCard({ title, instructor, lessons, level, rating, enrolledStudents }: CourseCardProps) {
return (
<article>
<h2>{title}</h2>
<p>Instructor: {instructor}</p>
<p>{lessons} Lessons</p>
<p>{level}</p>
<p>⭐ {rating}</p>
<p>{enrolledStudents.toLocaleString("en-US")} Students</p>
</article>
);
}
function Courses() {
return (
<div>
<CourseCard title="React" instructor="John" lessons={192} level="Beginner" rating={4.8} enrolledStudents={12450} />
<CourseCard title="TypeScript" instructor="Asha" lessons={133} level="Intermediate" rating={4.7} enrolledStudents={8320} />
<CourseCard title="Node.js" instructor="Ravi" lessons={120} level="Intermediate" rating={4.6} enrolledStudents={6015} />
</div>
);
}Fix the four prop mistakes: <CourseCard title=React lessons="192" course={ title: "React" } onStart={start()} />
Show hintHide hint
One missing pair of quotes, one wrong pair of quotes, one missing pair of braces, and one pair of parentheses too many.
Show solutionHide solution
<CourseCard
title="React"
lessons={192}
course={{ title: "React" }}
onStart={start}
/>A course page built from props
Split a course page into three components and pass every piece of data down from one parent.
- CoursePage owns all the data and renders CourseHeader, CourseStats, and CourseAction.
- CourseHeader takes title, level, and instructor.
- CourseStats takes lessons (number), duration (string), and students (number, shown as 12,450).
- CourseAction takes onStart and renders a Start Learning button that calls it.
- Every component has a props type.
function CoursePage() {
const course = {
title: "React",
level: "Beginner",
instructor: "John",
lessons: 192,
duration: "24 hours",
students: 12450,
};
function handleStart() {
console.log("Starting", course.title);
}
// render CourseHeader, CourseStats, CourseAction
}Show one solutionHide solution
type CourseHeaderProps = { title: string; level: string; instructor: string };
function CourseHeader({ title, level, instructor }: CourseHeaderProps) {
return (
<header>
<h1>{title}</h1>
<p>{level} · Instructor: {instructor}</p>
</header>
);
}
type CourseStatsProps = { lessons: number; duration: string; students: number };
function CourseStats({ lessons, duration, students }: CourseStatsProps) {
return (
<ul>
<li>{lessons} lessons</li>
<li>{duration}</li>
<li>{students.toLocaleString("en-US")} students</li>
</ul>
);
}
type CourseActionProps = { onStart: () => void };
function CourseAction({ onStart }: CourseActionProps) {
return <button onClick={onStart}>Start Learning</button>;
}
function CoursePage() {
const course = {
title: "React",
level: "Beginner",
instructor: "John",
lessons: 192,
duration: "24 hours",
students: 12450,
};
function handleStart() {
console.log("Starting", course.title);
}
return (
<main>
<CourseHeader title={course.title} level={course.level} instructor={course.instructor} />
<CourseStats lessons={course.lessons} duration={course.duration} students={course.students} />
<CourseAction onStart={handleStart} />
</main>
);
}Key points
- Props are values a parent passes to a child, written as attributes.
- The child receives them as one object - the first parameter.
- Quotes pass strings; braces pass any JavaScript value.
- A bare prop is true: <CourseCard published />.
- Type props with a named type; untyped props fail in strict TypeScript.
- Props flow one way, parent to child.
- Props are read-only - React throws in development if you write to them.
- Functions and JSX can be props too.
Quick check before you move on
Interview questions
What are props?
The inputs to a component. A parent passes them as JSX attributes, React collects them into one object, and the component receives that object as its first argument.
Are props mutable?
No. They belong to the parent. React freezes the props object in development, so writing to it throws; in production it would silently work, which is why you never do it. Values that change belong in state.
What is one-way data flow?
Data moves down the tree through props, from parent to child. Children cannot change what they receive; they ask the parent to change it by calling a function prop.
lessons="192" versus lessons={192}?
Quotes always pass a string; braces pass the JavaScript value, here a number. With TypeScript the string is a type error; without it, arithmetic silently goes wrong.
How do you type props in TypeScript?
Declare a type for the props object and annotate the parameter: function CourseCard({ title }: CourseCardProps). TypeScript then checks every place the component is used.
When would you pass an object instead of individual props?
When the component represents that object, like a CourseCard for a course. When the component is generic, pass only the fields it needs so it does not depend on a particular data shape.
Quiz
- 1.
What does <CourseCard lessons={100} /> pass? A. A string B. A number C. A boolean D. A function
- 2.
What does <CourseCard lessons="100" /> pass? A. A number B. A boolean C. A string D. An object
- 3.
In a Vite React TypeScript project, does this compile? function CourseCard({ title }) { return <h2>{title}</h2>; } A. Yes B. No
- 4.
Where does a child normally get its props from? A. The browser B. The database C. Its parent component D. CSS
- 5.
Can props hold functions? A. Yes B. No
- 6.
Which describes data flowing from parent to child? A. Props B. CSS C. State D. The DOM
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned