React

Chapter 03: Props & Component Communication — Passing and destructuring props, immutability, callback functions, child-to-parent communication, and default values.

1. What are Props?

Props (short for properties) are the mechanism by which parent components pass data and configuration down to child components. Just as you pass arguments to a regular JavaScript function to customize its output, you pass props to a React component to customize its rendered UI.

💡 Real-World Analogy: Custom Order Receipts & Function Parameters

Think of a component as a coffee barista function (brewCoffee(type, milk, sugar)):

  • The barista instructions (component definition) stay the exact same.
  • The customer (parent component) passes a custom order receipt (props): <CoffeeCup type="Espresso" milk="Oat" sugar={1} />.
  • The barista delivers a customized drink without changing their standard brewing technique!

Function Arguments vs React Props

// 1. Standard JavaScript Function:
function buildUserCard(name, role, rating) {
    return `${name} - ${role} (${rating}★)`;
}
buildUserCard("Anshuman", "Architect", 5);

// 2. React Functional Component with Props:
function UserCard(props) {
    return (
        <div className="user-card">
            <h3>{props.name}</h3>
            <p>Role: {props.role}</p>
            <span>Rating: {props.rating}★</span>
        </div>
    );
}

// Render with custom props:
<UserCard name="Anshuman" role="Architect" rating={5} />
Rendered Component Output:

Anshuman

Role: Architect

Rating: 5★

2. Passing & Destructuring Props

In modern React, developers almost always destructure props directly inside the function parameter signature. This makes the code significantly cleaner and allows setting convenient default values.

01

1. Clean In-Line Destructuring

Extract specific keys right in the function arguments:

function Button({ label, variant = "primary", disabled = false }) {
    return (
        <button className={`btn btn-${variant}`} disabled={disabled}>
            {label}
        </button>
    );
}
02

2. Props Spreading with Rest Operator (...)

Forward all remaining HTML attributes down to a native tag:

function CustomInput({ label, ...restProps }) {
    return (
        <div className="input-group">
            <label>{label}</label>
            <input {...restProps} />
        </div>
    );
}
// Usage: <CustomInput label="Age" type="number" min="18" />

3. Props Immutability (Pure Functions Rule)

A fundamental rule of React is that Props are strictly Read-Only and Immutable. A component must never modify its own props.

⚠️ Critical Rule: React Components Must Be Pure Functions with Respect to Props

// ❌ NEVER DO THIS: Direct prop mutation (Breaks React re-render tracking!)
function CartSummary(props) {
    props.total = props.total + 10; // 💥 TypeError: Cannot assign to read-only property!
    return <div>Total: ${props.total}</div>;
}

// ✅ CORRECT: Compute derived local values or use state
function CartSummary({ total, shippingCost = 10 }) {
    const grandTotal = total + shippingCost; // Pure derived calculation
    return <div>Grand Total: ${grandTotal}</div>;
}

4. Child-to-Parent Communication (Callback Functions)

Since data in React only flows downward (parent to child), how does a child notify a parent when a button is clicked or an input changes? The Parent passes a callback function as a prop down to the child!

Bidirectional Communication via Downward Callbacks
Parent: App Component Holds State: [theme, setTheme] Passes: onToggle={() => setTheme(...)} 1. Passes function 2. Invokes on click Child: ThemeToggle Component Receives: { onToggle } Calls: <button onClick={onToggle}>

Real-World Code Implementation

// 1. CHILD COMPONENT: Receives function via props and triggers it on user event
function SearchFilter({ onSearchChange }) {
    return (
        <input 
            type="text"
            placeholder="Type to filter..."
            onChange={(e) => onSearchChange(e.target.value)} // Sending data UP to parent
        />
    );
}

// 2. PARENT COMPONENT: Holds state and passes handler down
function ProductCatalog() {
    const [query, setQuery] = React.useState("");

    function handleFilter(searchText) {
        console.log("Parent received updated query:", searchText);
        setQuery(searchText);
    }

    return (
        <div className="catalog-wrapper">
            <h2>Catalog Search</h2>
            <SearchFilter onSearchChange={handleFilter} />
            <p>Active Filter: <strong>{query}</strong></p>
        </div>
    );
}
Interactive UI Output:

Catalog Search

Active Filter: React Hooks

5. Top Interview Questions & Common Pitfalls

⚠️ Pitfall 1: "What is Prop Drilling and how do you solve it?"

The Interview Answer: Prop Drilling occurs when you pass props down through multiple layers of intermediate components that don't need the data themselves just to reach a deeply nested child component (e.g., App -> Dashboard -> Sidebar -> UserAvatar).

Solutions:
1. Component Composition: Pass the component as children.
2. React Context API: Broadcast data globally (e.g. Theme, Auth, Language).
3. State Management: Redux Toolkit, Zustand, or Jotai for large enterprise apps.

⚠️ Pitfall 2: "What is the difference between Props and State?"

  • Props: Data passed into a component from its parent (External, Read-Only, Immutable).
  • State: Data managed inside a component (Internal, Mutable via setter function, triggers re-render).

6. Hands-On Practice Challenges

Master component communication and props handling with these interactive exercises. Click "Solve Challenge" to open the code editor!

🎯 Hands-on Coding Challenge Props & Defaults

Challenge 1: Default Props & Destructuring Engine

Write a component simulator function buildUserBadge(props) that destructures username, role = "Member", isVerified = false, and points = 0. Calculate the tier (GOLD $\ge 100$, SILVER $\ge 50$, else BRONZE) and return the badge data.

📋 Requirements & What to Write:
  • Destructure props with defaults for role, isVerified, and points.
  • Calculate tier level based on points thresholds.
  • Return displayName, verifiedTag, tierLevel, and badgeStyle object.
Expected Result: { displayName: "anshuman_dev [Member]", verifiedTag: "VERIFIED_USER", tierLevel: "GOLD", badgeStyle: { borderColor: "#10B981", color: "#B45309" } }
💡
Editor Instructions: Complete buildUserBadge and test with various prop combinations.
userBadge.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge Callbacks & State

Challenge 2: Simulate Child-to-Parent Callback Passing

Implement a closure createParentStore(initialCount) that returns an onIncrement(step) callback. Simulate a child button executing the callback with custom steps (e.g. $+5$, $+2$) and verify parent state synchronization.

📋 Requirements & What to Write:
  • Encapsulate count inside createParentStore.
  • Provide getCount() and onIncrement(step) methods.
  • Ensure executing onIncrement(5) correctly updates parent state.
Expected Result: [Parent Callback Triggered] Count is now: 15 | Final Parent State: 17
💡
Editor Instructions: Return an object with getCount and onIncrement.
callbackStore.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge Immutability Security

Challenge 3: Props Immutability Guard with Object.freeze

Write a function enforcePropsImmutability(propsObject) that returns a shallow-frozen copy of the props object using Object.freeze() to demonstrate how React protects components from accidental prop mutations.

📋 Requirements & What to Write:
  • Clone the input object shallowly using { ...propsObject }.
  • Freeze it with Object.freeze(...) and return it.
  • Verify with Object.isFrozen(...).
Expected Result: Is Protected: true | Props Value: "dark"
💡
Editor Instructions: Return an immutable shallow copy of props.
immutableProps.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...