Props
Passing Data Down the Component Tree
LinkedIn Hook
You build a
<UserCard>component. It works perfectly with hardcoded data.Then the interviewer says: "Now make it reusable for any user."
You reach for props. They follow up: "What happens if I mutate a prop inside the child?"
You hesitate. "Are props mutable or immutable? Why?"
Props are the backbone of React's data flow. Every component you write either receives props, passes props, or both. Yet most developers can't explain WHY props are read-only, WHEN prop drilling becomes a problem, or HOW render props work.
In this lesson, I break down props from the ground up: what they are, why they're immutable, destructuring patterns, default values, the children prop, and the render props pattern that powered React before hooks existed.
If you can't explain props confidently in an interview — this lesson fixes that.
Read the full lesson -> [link]
#React #JavaScript #WebDevelopment #InterviewPrep #Frontend #CodingInterview #Props #ReactJS #100DaysOfCode
What You'll Learn
- What props are and how they enable component reusability
- Why props are read-only and what happens if you try to mutate them
- How to destructure props and set default values
- What the
childrenprop is and how it enables composition - What prop drilling is and why it becomes a problem
- How the render props pattern works
1. What Are Props?
The Analogy
Think of a React component as a function and props as its arguments. Just like a function calculateArea(width, height) takes inputs and produces output, a component <UserCard name="Rakibul" role="developer" /> takes props and produces UI.
Here's the key insight: just as a function shouldn't modify its arguments (that would cause chaos), a component should never modify its props. Props flow in one direction — from parent to child — like water flowing downhill. The child receives the data but doesn't own it.
How Props Work
Props (short for "properties") are the mechanism React uses to pass data from a parent component to a child component. When you write JSX attributes on a component, React collects all of them into a single object and passes that object to the child component as the first argument.
// Parent component passes props via JSX attributes
function App() {
return <UserCard name="Rakibul" age={25} isAdmin={true} />;
}
// Child component receives all props as a single object
function UserCard(props) {
console.log(props);
// Output: { name: "Rakibul", age: 25, isAdmin: true }
return (
<div>
<h2>{props.name}</h2>
<p>Age: {props.age}</p>
<p>Role: {props.isAdmin ? "Admin" : "User"}</p>
</div>
);
}
You can pass any JavaScript value as a prop — strings, numbers, booleans, arrays, objects, functions, even other components.
function App() {
const hobbies = ["coding", "reading", "gaming"];
return (
<Profile
name="Rakibul" // string
age={25} // number
isActive={true} // boolean
hobbies={hobbies} // array
address={{ city: "Dhaka" }} // object (double braces: outer = JSX, inner = object)
onEdit={() => console.log("edit")} // function
/>
);
}
2. Props Are Read-Only
This is the single most important rule about props and one of the most common interview questions.
A component must never modify its own props. React enforces a strict rule: components must act as "pure functions" with respect to their props. They take props in, render output, and never change the input.
// WRONG — never do this
function Greeting(props) {
props.name = "Modified"; // This will throw an error in strict mode
return <h1>Hello, {props.name}</h1>;
}
// RIGHT — use props as read-only
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
Why are props read-only?
- Predictability — If a child could change its props, the parent's data would change unexpectedly. You'd have no idea where your data was modified.
- One-way data flow — React's entire architecture depends on data flowing downward. If children could modify props, data would flow in both directions and the app would become impossible to debug.
- Re-rendering control — React decides when to re-render based on new props from the parent. If the child mutated props directly, React wouldn't know something changed.
If a child needs to change data, it doesn't modify the prop — it calls a callback function that the parent passed down as a prop:
function Parent() {
const [name, setName] = React.useState("Rakibul");
// Parent passes both the data AND the function to change it
return <Child name={name} onNameChange={setName} />;
}
function Child({ name, onNameChange }) {
// Child doesn't modify the prop — it asks the parent to change it
return (
<div>
<p>Name: {name}</p>
<button onClick={() => onNameChange("Updated!")}>
Change Name
</button>
</div>
);
}
3. Destructuring Props
Writing props.name, props.age, props.onClick everywhere is verbose. Destructuring makes your code cleaner and immediately shows what a component expects.
// Without destructuring — verbose
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>{props.email}</p>
<button onClick={props.onDelete}>Delete</button>
</div>
);
}
// With destructuring in the parameter — clean and self-documenting
function UserCard({ name, email, onDelete }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
<button onClick={onDelete}>Delete</button>
</div>
);
}
// Destructuring with renaming
function UserCard({ name: userName, email: userEmail }) {
return <p>{userName} — {userEmail}</p>;
}
// Destructuring with rest operator — grab specific props, forward the rest
function UserCard({ name, email, ...rest }) {
// rest contains every other prop (className, style, onClick, etc.)
return (
<div {...rest}>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
Destructuring in the function parameter is the most common pattern in modern React code. It acts as built-in documentation — anyone reading the function signature immediately knows what data this component needs.
4. Default Props
Sometimes a prop is optional. You want a fallback value if the parent doesn't pass it.
Using Default Parameter Values (Modern Way)
// Default values in destructuring — the standard modern approach
function Button({ label = "Click Me", size = "medium", variant = "primary" }) {
return (
<button className={`btn btn-${size} btn-${variant}`}>
{label}
</button>
);
}
// Usage
<Button /> // label="Click Me", size="medium", variant="primary"
<Button label="Submit" /> // label="Submit", size="medium", variant="primary"
<Button label="Cancel" size="lg" /> // label="Cancel", size="lg", variant="primary"
Using defaultProps (Legacy — Still Asked in Interviews)
function Button({ label, size }) {
return <button className={`btn btn-${size}`}>{label}</button>;
}
Button.defaultProps = {
label: "Click Me",
size: "medium",
};
// This approach is deprecated in React 18.3+ and will be removed in React 19
// Interviewers may ask about it to test whether you know it's deprecated
Interview trap: defaultProps only triggers for undefined, not for null. If a parent passes null explicitly, the default is NOT used. This is the same behavior as JavaScript default parameters.
function Greeting({ name = "Guest" }) {
return <h1>Hello, {name}</h1>;
}
<Greeting /> // "Hello, Guest" — undefined triggers default
<Greeting name={undefined} /> // "Hello, Guest" — undefined triggers default
<Greeting name={null} /> // "Hello, " — null does NOT trigger default
<Greeting name="" /> // "Hello, " — empty string does NOT trigger default
5. The Prop Drilling Problem
Prop drilling happens when you need to pass data through multiple levels of components that don't actually need that data — they just forward it to their children.
// Prop drilling — "theme" passes through Header and NavBar unnecessarily
function App() {
const [theme, setTheme] = React.useState("dark");
return <Header theme={theme} />;
}
function Header({ theme }) {
// Header doesn't use theme — just passes it down
return <NavBar theme={theme} />;
}
function NavBar({ theme }) {
// NavBar doesn't use theme either — just passes it down
return <ThemeToggle theme={theme} />;
}
function ThemeToggle({ theme }) {
// Finally! This component actually uses theme
return <button className={theme}>Current: {theme}</button>;
}
Why is this a problem?
- Maintenance nightmare — If
ThemeToggleneeds a new prop, you must updateApp,Header, ANDNavBareven though onlyAppandThemeTogglecare about it. - Tight coupling — Intermediate components become dependent on props they don't use.
- Readability — It becomes hard to track where data originates.
When is prop drilling acceptable?
Prop drilling through 2-3 levels is perfectly fine. It's explicit, easy to follow, and doesn't need a complex solution. The problem starts at 4+ levels or when many props need to flow through unrelated components.
Solutions (covered in later lessons):
useContext(Lesson 3.5) — React's built-in solution- State management libraries — Redux, Zustand (Chapter 6)
- Component composition — restructure your tree so deep passing isn't needed
6. Children as Props
The children prop is a special prop that React passes automatically. It contains whatever you put between the opening and closing tags of a component.
// Anything between <Card> and </Card> becomes props.children
function Card({ children, title }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">
{children}
</div>
</div>
);
}
// Usage — the content between tags is "children"
function App() {
return (
<Card title="User Info">
<p>Name: Rakibul</p>
<p>Role: Developer</p>
</Card>
);
}
// Output:
// <div class="card">
// <h3>User Info</h3>
// <div class="card-body">
// <p>Name: Rakibul</p>
// <p>Role: Developer</p>
// </div>
// </div>
children enables the composition pattern — building complex UIs by nesting simple components. This is how layout components, wrappers, and providers work:
// Layout component using children
function PageLayout({ children }) {
return (
<div className="page">
<header>My App</header>
<main>{children}</main>
<footer>Copyright 2026</footer>
</div>
);
}
// Modal wrapper using children
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>
);
}
// Usage
function App() {
return (
<PageLayout>
<h1>Welcome!</h1>
<Modal isOpen={true} onClose={() => {}}>
<p>This is modal content</p>
</Modal>
</PageLayout>
);
}
Interview insight: children can be anything — a string, a number, a JSX element, an array of elements, a function (this is render props), or even undefined. React is flexible about what children can be.
7. Render Props Pattern
The render props pattern is a technique where a component receives a function as a prop and calls that function to determine what to render. The function gives the child's internal data back to the parent, letting the parent decide the UI.
The Analogy
Think of a render prop like a vending machine with a customizable display. The machine (component) handles all the logic — tracking inventory, accepting payment, dispensing items. But instead of having a fixed screen, it lets YOU (the parent) decide what the screen shows. The machine passes its internal data (inventory count, selected item) to your display function.
// MouseTracker handles the logic (tracking mouse position)
// but lets the parent decide what to render with that data
function MouseTracker({ render }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
// Call the render function with our internal state
return render(position);
}
// Usage — parent decides what to display
function App() {
return (
<div>
{/* Show coordinates as text */}
<MouseTracker
render={({ x, y }) => (
<p>Mouse is at ({x}, {y})</p>
)}
/>
{/* Same logic, different UI — show a dot that follows the cursor */}
<MouseTracker
render={({ x, y }) => (
<div
style={{
position: "fixed",
left: x - 5,
top: y - 5,
width: 10,
height: 10,
borderRadius: "50%",
background: "red",
}}
/>
)}
/>
</div>
);
}
Using children as a Render Prop
Instead of a render prop, you can use children as the function:
function MouseTracker({ children }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
// Call children as a function
return children(position);
}
// Usage — cleaner syntax
function App() {
return (
<MouseTracker>
{({ x, y }) => <p>Mouse is at ({x}, {y})</p>}
</MouseTracker>
);
}
Important context: The render props pattern was the dominant way to share logic between components before React hooks. Today, custom hooks (Lesson 3.7) solve the same problem more cleanly. However, render props still appear in libraries (React Router, Downshift, Formik) and interviewers test this pattern frequently.
Common Mistakes
1. Mutating Props Directly
// WRONG — this breaks React's data flow
function UserCard(props) {
props.name = props.name.toUpperCase(); // Never mutate props!
return <h1>{props.name}</h1>;
}
// RIGHT — create a derived value
function UserCard({ name }) {
const displayName = name.toUpperCase(); // Create a new variable
return <h1>{displayName}</h1>;
}
Why it's wrong: Props are owned by the parent. Mutating them violates React's one-way data flow, causes unpredictable behavior, and can lead to bugs where the parent's data silently changes.
2. Passing Objects/Arrays Inline (Unnecessary Re-renders)
// PROBLEM — creates a new object on every render
function App() {
return <UserCard style={{ color: "red" }} data={[1, 2, 3]} />;
// { color: "red" } is a NEW object every render
// [1, 2, 3] is a NEW array every render
// This means UserCard sees "new" props every time and re-renders
}
// BETTER — define outside the render or use useMemo
const cardStyle = { color: "red" };
const data = [1, 2, 3];
function App() {
return <UserCard style={cardStyle} data={data} />;
}
This matters when the child component is wrapped in React.memo — the memo optimization won't work if you pass new object/array references every render.
3. Confusing Props with State
Props and state are both plain JavaScript objects, but they serve completely different purposes:
- Props = data passed FROM parent. The component cannot change them.
- State = data managed BY the component itself. The component can change them.
A common mistake is trying to "sync" state from props. If you need the prop value to change, the parent should change it — not the child.
Interview Questions
Q: What are props in React? How do they differ from state?
Props are read-only data passed from parent to child. State is mutable data owned and managed by the component itself. Props flow down (parent to child), while state changes trigger re-renders in the component that owns it. A component cannot modify its own props, but it can modify its own state.
Q: Why are props read-only?
Q: What is prop drilling? When does it become a problem, and what are the solutions?
Q: Explain the render props pattern. Why was it popular before hooks?
Render props is a pattern where a component accepts a function as a prop and calls it to render UI. The function receives the component's internal data, letting the parent decide what to display. It was popular because it solved the code reuse problem — sharing stateful logic between components without inheritance. Custom hooks now solve the same problem more elegantly, but render props still appear in many libraries.
Q: What is the children prop? How does it differ from other props?
Quick Reference — Cheat Sheet
┌─────────────────────────────────────────────────────────────────┐
│ PROPS — CHEAT SHEET │
├─────────────────────────────────────────────────────────────────┤
│ │
│ BASICS │
│ <Child name="X" age={25} /> → pass props as JSX attributes │
│ function Child(props) → receive as single object │
│ function Child({ name, age }) → destructure in parameter │
│ │
│ RULES │
│ ✓ Props are READ-ONLY — never mutate them │
│ ✓ Data flows ONE WAY — parent → child │
│ ✓ To "change" props, call a parent callback │
│ ✓ Any JS value can be a prop (string, fn, object, JSX) │
│ │
│ DEFAULT VALUES │
│ function Btn({ size = "md" }) → default in destructuring │
│ Btn.defaultProps = { size } → legacy (deprecated in 18.3+) │
│ null does NOT trigger default → only undefined does │
│ │
│ CHILDREN │
│ <Card>content</Card> → content = props.children │
│ children can be: string, JSX, array, function, undefined │
│ │
│ PROP DRILLING │
│ A → B → C → D (only D needs the data) │
│ 2-3 levels = fine | 4+ levels = consider context/state mgmt │
│ │
│ RENDER PROPS │
│ <Mouse render={({x,y}) => <p>{x},{y}</p>} /> │
│ <Mouse>{({x,y}) => <p>{x},{y}</p>}</Mouse> (children as fn) │
│ Pre-hooks pattern → now prefer custom hooks │
│ │
│ PROPS vs STATE │
│ ┌──────────┬──────────────────┬──────────────────┐ │
│ │ │ Props │ State │ │
│ ├──────────┼──────────────────┼──────────────────┤ │
│ │ Owner │ Parent │ Component itself │ │
│ │ Mutable? │ No (read-only) │ Yes (setState) │ │
│ │ Flow │ Parent → Child │ Internal │ │
│ │ Purpose │ Configure child │ Component memory │ │
│ └──────────┴──────────────────┴──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Previous: Lesson 1.4 — Component Types & Composition -> Next: Lesson 2.2 — State — Component Memory ->
This is Lesson 2.1 of the React Interview Prep Course — 10 chapters, 42 lessons.