React Components and Props
Web DevelopmentLesson 2 of 64 min read
What you'll learn
- Pass data into a component using props and read it with destructuring
- Type props with TypeScript and set default values for optional props
- Compose components using the special
childrenprop - Explain one-way data flow and why props are read-only
On this page
A single hard-coded profile card is not useful. A card that accepts a name, a role and a list of skills can be reused for every person in your app. Props are how that data gets in.
Props: the inputs to a component
A component is a function, and props are its single argument: an object holding whatever the parent passed in. When you write <ProfileCard name="Asha" />, React calls ProfileCard({ name: "Asha" }). Almost everyone destructures the object in the parameter list so the component body reads naturally.
Props can be any JavaScript value: strings, numbers, booleans, arrays, objects and even functions. Strings can be written in quotes; everything else goes in braces. A boolean prop written without a value, like isOpenToWork, means true.
type ProfileCardProps = {
name: string;
role: string;
skills?: string[];
isOpenToWork?: boolean;
};
export default function ProfileCard({
name,
role,
skills = [],
isOpenToWork = false,
}: ProfileCardProps) {
return (
<article>
<h3>{name}</h3>
<p>{role}</p>
{isOpenToWork ? <strong>Open to work</strong> : null}
<p>Skills: {skills.length > 0 ? skills.join(", ") : "none listed yet"}</p>
</article>
);
}import ProfileCard from "./ProfileCard";
export default function App() {
return (
<main>
<ProfileCard
name="Asha Verma"
role="Frontend Developer"
skills={["React", "CSS"]}
isOpenToWork
/>
<ProfileCard name="Ravi Nair" role="Backend Developer" />
</main>
);
}Output
Asha Verma Frontend Developer Open to work Skills: React, CSS Ravi Nair Backend Developer Skills: none listed yet
Default values and optional props
The ? in skills?: string[] marks a prop as optional, and the = [] in the destructuring supplies a fallback when the parent leaves it out. This is the idiomatic way to set defaults in modern React. You may see Component.defaultProps = {...} in older tutorials, but React 19 no longer supports it for function components, so use default parameter values instead.
Defaults only apply when the value is undefined. If a parent passes null or an empty string on purpose, the default is skipped. Decide per prop whether null should be allowed, and type it accordingly (skills: string[] | null).
Composition with the children prop
Anything you put between a component's opening and closing tags arrives as the special children prop. This lets you build wrapper components, such as a card or a modal, that do not need to know what goes inside them. Composition like this is usually a better fit than building a component with dozens of configuration props.
import type { ReactNode } from "react";
type CardProps = {
title: string;
children: ReactNode;
};
export default function Card({ title, children }: CardProps) {
return (
<section style={{ border: "1px solid #ccc", padding: 16, borderRadius: 8 }}>
<h2>{title}</h2>
{children}
</section>
);
}
// Usage
// <Card title="Candidates">
// <ProfileCard name="Asha Verma" role="Frontend Developer" />
// <p>2 more candidates match your filters.</p>
// </Card>Output
Candidates (card heading, inside a bordered box) Asha Verma Frontend Developer 2 more candidates match your filters.
One-way data flow
Data in React flows down: a parent passes props to its children, never the other way. Props are also read-only. A component must not change the props it receives, just as a pure function must not change its arguments. This rule is what makes React apps predictable: when something looks wrong on screen, you can trace the value upward through the parents until you find where it came from.
So how does a child tell its parent something happened? The parent passes a function as a prop and the child calls it. Data goes down as props, events go up as function calls.
type Candidate = { id: number; name: string };
type CandidateListProps = {
candidates: Candidate[];
onSelect: (name: string) => void;
};
function CandidateList({ candidates, onSelect }: CandidateListProps) {
return (
<ul>
{candidates.map((c) => (
<li key={c.id}>
<button onClick={() => onSelect(c.name)}>{c.name}</button>
</li>
))}
</ul>
);
}
export default function App() {
const people: Candidate[] = [
{ id: 1, name: "Asha Verma" },
{ id: 2, name: "Ravi Nair" },
];
return (
<CandidateList
candidates={people}
onSelect={(name) => console.log("Selected", name)}
/>
);
}Output
[Asha Verma] [Ravi Nair] (two buttons) Console after clicking the second button: Selected Ravi Nair
Right now the selection is only logged. In React useState Hook: Managing State you will store it in the parent so the UI can react to it.
Common mistakes
- Mutating a prop. Writing
props.skills.push("Go")changes the parent's data directly. Symptom: values change in places you did not expect and updates do not re-render. Copy first:[...skills, "Go"]. - Passing a string where a number is expected.
<Counter start="5" />passes the string"5", sostart + 1gives"51". Usestart={5}. - Calling the function instead of passing it.
onSelect={handle(name)}runs during render;onSelect={() => handle(name)}runs on click. Symptom: the handler fires immediately or causes an infinite loop. - Forgetting that `children` is not required in the type. If you write
children?: ReactNodethe wrapper may render empty. Decide whether it is mandatory. - Prop drilling forever. Passing the same value through five layers is a smell. Try composition first (pass the finished element as
children), and reach for context only when many distant components need the same value.
How this shows up in interviews and real projects
"Props vs state" is one of the most common React interview questions. The short answer: props are inputs owned by the parent and read-only for the child, while state is data a component owns and can change. In real code, good component design is mostly deciding what the props of each piece should be. If you want to practise explaining these ideas out loud, the coding interview prep guide for self-taught developers has a useful routine.
Try it yourself
Build a NotificationBell component with a required count prop and an optional label prop that defaults to "Notifications". Wrap two of them in a Panel component that accepts children and a title prop.
Show a hint
Type the props with a type alias, give label a default in the destructuring, and import ReactNode as a type for children.
Key takeaways
- Props are the single argument of a component function; destructure them in the parameter list.
- Set defaults with default parameter values, not
defaultProps, which React 19 ignores for function components. - The
childrenprop lets you build flexible wrapper components through composition. - Data flows down as props and events flow up as callback props; never mutate props.
Test yourself
A trick from this lesson's ride: Whatever sits between the tags is the filling that the wrapper serves as children. Four more tricks like it wait along the route, each followed by a question.
Frequently asked questions
What are props in React?
Props are the inputs a parent passes to a component, delivered as a single object argument. They can hold any JavaScript value, including functions, and the component should treat them as read-only.
What is the difference between props and state in React?
Props are passed in from a parent and cannot be changed by the receiving component. State is private data that a component owns and updates itself using a setter such as the one from useState. Changing either triggers a re-render.
How do I pass a function as a prop in React?
Pass the function reference without calling it, for example onSelect={handleSelect}, or wrap it in an arrow function when you need arguments, such as onSelect={() => handleSelect(id)}. The child then calls it like any other function, usually inside an event handler.