Props 📨

1. Introduction 👋

Props (short for properties) are how data flows from parent to child components in React. They allow components to be configurable and reusable, forming the backbone of React's component communication model. This tutorial covers everything from passing basic values to advanced patterns like prop spreading and TS validation.

Information

This tutorial builds on Components and State. Reviewing those first will help you understand how props fit into the bigger picture.

2. What are Props? 🤔

Props are read-only inputs passed into a component, similar to arguments passed into a function. They let a parent component customize how a child component behaves and appears.

Code Snippet

function Greeting(props) {
  return <h1>Hello, {props.name}! 👋</h1>;
}

<Greeting name="Alice" />
>>"Props are the information that you pass to a JSX tag." — React Documentation

3. Why Props? 💡

  • Reusability: The same component renders differently based on the props it receives.
  • Composition: Parent components configure and combine child components flexibly.
  • Predictability: Since props are read-only, data flow remains clear and traceable.
  • Separation of Concerns: Child components stay generic and reusable, unaware of where their data comes from.

4. Passing Props 📤

Props are passed to a component the same way you'd set attributes on an HTML element — using a name={value} syntax within the JSX tag.

Code Snippet

<UserCard name="Alice" age={28} isVerified={true} />
  • String values can use quotes directly: name="Alice".
  • Non-string values require curly braces: age={28}.

5. Receiving Props đŸ“Ĩ

A function component receives all its props bundled into a single object, typically named props, as its first argument.

Code Snippet

function UserCard(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>Age: {props.age}</p>
    </div>
  );
}

6. Reading Props 👀

Props are accessed like regular object properties. Most commonly, they're destructured directly in the function's parameter list for cleaner code.

Code Snippet

// Accessing via props object
function UserCard(props) {
  return <h2>{props.name}</h2>;
}

// Destructured directly (preferred)
function UserCard({ name }) {
  return <h2>{name}</h2>;
}

Tip

Destructuring props in the function signature is the most common and readable convention in modern React code.

7. Props vs State âš”ī¸

AspectPropsState
OwnershipPassed in from a parentOwned by the component itself
MutabilityRead-only from the child's viewCan be updated via setter functions
PurposeConfigures a component externallyTracks internal, changing data
DirectionFlows downward, parent → childLocal to the owning component

8. Multiple Props đŸ”ĸ

Code Snippet

function ProductCard({ name, price, inStock, imageUrl }) {
  return (
    <div className="product-card">
      <img src={imageUrl} alt={name} />
      <h3>{name}</h3>
      <p>${price.toFixed(2)}</p>
      <p>{inStock ? "In Stock ✅" : "Out of Stock ❌"}</p>
    </div>
  );
}

<ProductCard name="Headphones" price={79.99} inStock={true} imageUrl="/headphones.jpg" />

9. Default Props đŸŽ¯

In function components, default values are provided directly in the destructured parameter list, ensuring a fallback when a prop isn't passed.

Code Snippet

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

<Button />                          {/* Uses both defaults */}
<Button label="Save" />             {/* Uses default variant */}
<Button label="Delete" variant="danger" />

Note

The older Component.defaultProps static property is now deprecated for function components — use default parameter values instead.

10. Children Prop đŸ‘ļ

children is a special prop that represents whatever content is nested between a component's opening and closing tags.

Code Snippet

function Card({ children }) {
  return <div className="card">{children}</div>;
}

<Card>
  <h2>Title</h2>
  <p>Some content inside the card.</p>
</Card>

Tip

The children prop is what makes composition possible — it lets components wrap arbitrary content without knowing its structure in advance.

11. Passing JSX as Props 🧩

Beyond children, any prop can accept JSX as its value, enabling flexible, named "slots" within a component.

Code Snippet

function PageLayout({ header, footer, children }) {
  return (
    <div>
      <header>{header}</header>
      <main>{children}</main>
      <footer>{footer}</footer>
    </div>
  );
}

<PageLayout
  header={<h1>My Site</h1>}
  footer={<p>Š 2026</p>}
>
  <p>Main page content</p>
</PageLayout>

12. Passing Components as Props đŸ§Ŧ

Entire component references can be passed as props, letting a parent decide which component a child should render — a powerful pattern for flexible layouts and icons.

Code Snippet

function Alert({ Icon, message }) {
  return (
    <div className="alert">
      <Icon />
      <span>{message}</span>
    </div>
  );
}

<Alert Icon={WarningIcon} message="Low disk space" />

Note

When passing a component reference as a prop, capitalize the prop name (e.g., Icon) so React recognizes it as a renderable component when used as a JSX tag.

13. Passing Functions as Props 🔗

Functions are commonly passed as props to let child components communicate events back up to their parent — since data itself only flows downward.

Code Snippet

function SearchBar({ onSearch }) {
  const [query, setQuery] = useState("");

  return (
    <input
      value={query}
      onChange={(e) => {
        setQuery(e.target.value);
        onSearch(e.target.value);
      }}
    />
  );
}

<SearchBar onSearch={(query) => console.log("Searching for:", query)} />

14. Passing Objects as Props đŸ“Ļ

Code Snippet

function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

<UserProfile user={{ name: "Alice", email: "alice@example.com" }} />

Tip

For components requiring many related fields, passing a single object prop can be cleaner than passing each field as a separate individual prop.

15. Passing Arrays as Props 📚

Code Snippet

function TagList({ tags }) {
  return (
    <div>
      {tags.map((tag) => <span key={tag} className="tag">{tag}</span>)}
    </div>
  );
}

<TagList tags={["react", "javascript", "webdev"]} />

16. Passing Styles as Props 🎨

Code Snippet

function Box({ style, children }) {
  return <div style={{ padding: "16px", ...style }}>{children}</div>;
}

<Box style={{ backgroundColor: "#f0f9ff", borderRadius: "8px" }}>
  Content here
</Box>

Tip

Spreading a base style object before the incoming style prop (as shown above) lets consumers override specific properties without needing to redefine everything.

17. Passing Event Handlers as Props đŸ–ąī¸

Code Snippet

function DeleteButton({ onDelete, itemId }) {
  return <button onClick={() => onDelete(itemId)}>Delete</button>;
}

function TodoItem({ id, text, onDelete }) {
  return (
    <li>
      {text}
      <DeleteButton itemId={id} onDelete={onDelete} />
    </li>
  );
}

Best Practice

Name handler props starting with on (e.g., onDelete, onSubmit) to clearly signal that they're callback functions, following React's own convention.

18. Prop Drilling đŸ•ŗī¸

Prop drilling occurs when a prop must be passed through several intermediate components that don't actually use it, just to reach a deeply nested child.

Code Snippet

function App() {
  const [user, setUser] = useState({ name: "Alice" });
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Sidebar user={user} />; // Layout doesn't use 'user' itself
}

function Sidebar({ user }) {
  return <UserBadge user={user} />; // neither does Sidebar
}

function UserBadge({ user }) {
  return <p>{user.name}</p>; // finally used here
}

Warning

Excessive prop drilling makes refactoring harder and clutters intermediate components with props they don't need. Consider the Context API for deeply shared data.

19. Immutable Props 🔒

Props are read-only — a component must never modify the props it receives. If a value needs to change, it should be lifted into state in the owning component instead.

Code Snippet

function Bad({ count }) {
  count = count + 1; // ❌ Never mutate props directly
  return <p>{count}</p>;
}

function Good({ count }) {
  const displayCount = count + 1; // ✅ Derive a new value instead
  return <p>{displayCount}</p>;
}

Danger

Mutating props directly violates React's one-way data flow and can cause unpredictable behavior, since the parent remains unaware of the change.

20. Props Destructuring 📤

Code Snippet

// Without destructuring
function UserCard(props) {
  return <h2>{props.name} ({props.role})</h2>;
}

// With destructuring (preferred)
function UserCard({ name, role }) {
  return <h2>{name} ({role})</h2>;
}

Tip

Destructuring also pairs naturally with default values: { name, role = "Member" }.

21. Rest Props ➕

The rest operator (...rest) collects any remaining props not explicitly destructured, useful for forwarding extra attributes to an underlying element.

Code Snippet

function TextInput({ label, ...rest }) {
  return (
    <div>
      <label>{label}</label>
      <input {...rest} />
    </div>
  );
}

<TextInput label="Email" type="email" placeholder="you@example.com" required />

Note

Here, type, placeholder, and required are all collected into rest and forwarded to the native <input> element.

22. Spreading Props 🌊

The spread operator ({...props}) passes all properties of an object as individual props at once, useful for forwarding or reusing prop sets.

Code Snippet

const buttonProps = { label: "Submit", variant: "primary", disabled: false };

<Button {...buttonProps} />

// Equivalent to:
<Button label="Submit" variant="primary" disabled={false} />

Caution

Overusing prop spreading can obscure exactly which props a component receives, making the code harder to trace — use it deliberately, not as a default habit.

23. Conditional Props 🔀

Code Snippet

function SubmitButton({ isLoading }) {
  return (
    <Button
      label={isLoading ? "Submitting..." : "Submit"}
      disabled={isLoading}
    />
  );
}

24. Dynamic Props 🔄

Props can be computed dynamically based on other data, then spread or passed individually, allowing components to adapt based on runtime conditions.

Code Snippet

function DynamicField({ fieldConfig }) {
  const inputProps = {
    type: fieldConfig.type,
    placeholder: fieldConfig.placeholder,
    ...(fieldConfig.type === "number" && { min: 0, max: 100 }),
  };

  return <input {...inputProps} />;
}

25. Optional Props ❓

Not every prop needs to be required. Optional props typically have a default value or are conditionally checked before use.

Code Snippet

function Avatar({ src, size = 40, alt = "User avatar" }) {
  return (
    <img
      src={src || "/default-avatar.png"}
      width={size}
      height={size}
      alt={alt}
    />
  );
}

26. Props Validation ✅

PropTypes is a runtime validation library that can check prop types during development and warn about mismatches in the console.

Code Snippet

import PropTypes from 'prop-types';

function UserCard({ name, age }) {
  return <h2>{name} ({age})</h2>;
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
};

Best Practice

For new projects, TypeScript is generally preferred over PropTypes since it validates props at compile time rather than at runtime.

27. TypeScript with Props 🔷

TS lets you define an explicit interface or type describing exactly what props a component accepts.

Code Snippet

interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary" | "danger";
  onClick: () => void;
  disabled?: boolean;
}

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

Tip

Use ? to mark props as optional in the interface — this pairs naturally with default parameter values in the function signature.

28. Props Best Practices 🌟

  1. Keep prop names clear and descriptive, avoiding overly generic names like data or info.
  2. Destructure props directly in the function signature for readability.
  3. Provide sensible default values for optional props.
  4. Never mutate props — treat them as strictly read-only.
  5. Avoid passing too many individual props — group related data into an object when it makes sense.
  6. Use Context to avoid excessive prop drilling for deeply shared data.

29. Common Props Mistakes đŸšĢ

  • Mutating props directly instead of deriving new values.
  • Forgetting to provide default values for optional props, leading to undefined errors.
  • Excessive prop drilling through many layers of unrelated components.
  • Overusing the spread operator, obscuring which props a component actually expects.
  • Passing new inline object or array literals as props on every render, causing unnecessary re-renders in memoized children.

Danger

Passing a new inline object like style={{ color: "red" }} creates a brand-new reference on every render, which can defeat memo optimizations on the receiving component.

30. Frequently Asked Questions ❓

Question

Can a child component modify the props it receives?

Answer

No. Props are read-only. If a value needs to change, it should live in state, either in the child itself or lifted to a shared parent.

Question

What's the difference between children and a regular prop?

Answer

children is simply a special prop automatically populated with whatever content is nested between a component's opening and closing tags — it works identically to any other prop otherwise.

Question

When should I use Context instead of passing props?

Answer

Reach for Context when a value is needed by many components at different nesting levels, to avoid excessive prop drilling through components that don't otherwise need that data.

31. Summary 📝

Props enable React's component-based architecture by allowing data to flow predictably from parent to child. From simple string values to functions, objects, and even other components, mastering how to pass, destructure, and validate props is essential for building flexible, reusable UI.

Summary

With props and state both covered, the next logical step is exploring the Context API for sharing data across distant components, and Forms, which heavily combine props, state, and event handling together.