React BasicsJanuary 06, 2025

React Props - Complete Guide

Learn what React Props are and how to use them effectively. Understand prop types, default props, prop validation, and best practices for passing data between components.

Author: Sushil Kumar

reactpropscomponentsdata-flow

React Props - Complete Guide

Props (short for "properties") are React's way of passing data from parent components to child components. Think of them as arguments you pass to a function, but for React components. They're one of the fundamental concepts you need to master to build dynamic and reusable React applications.


What are Props?

Props are read-only data that flow from parent components down to child components. They make your components flexible and reusable - instead of hardcoding values inside a component, you pass them in as props, making the same component work with different data.

Imagine you have a Button component. Instead of creating separate components for "Submit", "Cancel", and "Delete" buttons, you can create one Button component and pass different text as props. That's the power of props!


Basic Props Example

Let's start with a simple example to see how props work:

function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}
 
function App() {
  return <Greeting name="John" />;
}

Here, we're passing the name prop from the App component to the Greeting component. The Greeting component receives it through the props parameter and uses it to display a personalized message.


Destructuring Props

Instead of accessing props through props.name, you can destructure them directly in the function parameters. This is cleaner and more readable:

function UserCard({ name, email, age }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Email: {email}</p>
      <p>Age: {age}</p>
    </div>
  );
}
 
function App() {
  return (
    <UserCard 
      name="John Doe"
      email="john@example.com"
      age={25}
    />
  );
}

Destructuring makes it immediately clear what props a component expects, and it's the preferred way to write components in modern React.


Default Props

Sometimes you want props to have default values. This is useful when a prop is optional but you want to ensure it always has a value. There are two ways to set default props:

Using Default Parameters

The modern approach is to use default parameter values directly in the function signature:

function Button({ text, color = 'blue' }) {
  return (
    <button style={{ backgroundColor: color }}>
      {text}
    </button>
  );
}
 
// Usage
<Button text="Click me" /> // color will be 'blue'
<Button text="Click me" color="red" /> // color will be 'red'

This is clean and straightforward. If color isn't provided, it defaults to 'blue'.

Using defaultProps

You can also use the defaultProps property, though the default parameter approach is more common now:

function Button({ text, color }) {
  return (
    <button style={{ backgroundColor: color }}>
      {text}
    </button>
  );
}
 
Button.defaultProps = {
  color: 'blue',
  text: 'Click me'
};

Both approaches work, but default parameters are generally preferred because they're more concise and easier to read.


The Children Prop

The children prop is special in React. It represents the content placed between the opening and closing tags of a component:

function Card({ children, title }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-content">
        {children}
      </div>
    </div>
  );
}
 
function App() {
  return (
    <Card title="My Card">
      <p>This is the card content</p>
      <button>Click me</button>
    </Card>
  );
}

Everything between <Card> and </Card> becomes the children prop. This is incredibly useful for creating wrapper components like modals, cards, or layout components.


Different Types of Props

Props can be of any JavaScript type. Here are the most common ones:

Strings

<Component name="John" />

Numbers

<Component age={25} count={10} />

Notice the curly braces for numbers - they're required because JSX treats everything in quotes as a string.

Booleans

<Component isActive={true} showDetails={false} />

You can also pass booleans without the value when it's true:

<Component isActive /> // same as isActive={true}

Arrays

<Component items={[1, 2, 3]} users={['John', 'Jane']} />

Objects

<Component user={{ name: 'John', age: 25 }} />

Functions

Functions are commonly passed as props, especially for event handlers:

function App() {
  const handleClick = () => {
    console.log('Button clicked!');
  };
 
  return <Button onClick={handleClick} />;
}

Props Validation

Validating props helps catch errors early and makes your components more self-documenting. There are two main approaches:

Using TypeScript

TypeScript provides compile-time type checking:

interface ButtonProps {
  text: string;
  onClick: () => void;
  disabled?: boolean;
}
 
function Button({ text, onClick, disabled = false }: ButtonProps) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {text}
    </button>
  );
}

TypeScript will warn you if you use the component incorrectly, and many IDEs will provide autocomplete for props.

Using PropTypes

If you're not using TypeScript, PropTypes is a great alternative:

import PropTypes from 'prop-types';
 
function Button({ text, onClick, disabled }) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {text}
    </button>
  );
}
 
Button.propTypes = {
  text: PropTypes.string.isRequired,
  onClick: PropTypes.func.isRequired,
  disabled: PropTypes.bool,
};

PropTypes will show warnings in the console if props don't match the expected types, which is helpful during development.


Using Spread Operator with Props

The spread operator (...) is useful when you want to pass multiple props or forward props to child elements:

function UserCard({ name, email, ...otherProps }) {
  return (
    <div {...otherProps}>
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
}
 
// Usage
<UserCard 
  name="John"
  email="john@example.com"
  className="card"
  id="user-1"
/>

Here, className and id are collected into otherProps and spread onto the div. This is useful for creating flexible wrapper components.


Key takeaway: Props are for data that comes from outside the component, while state is for data that the component manages internally.


Best Practices

Here are some tips to help you use props effectively:

Always Destructure Props

Destructuring makes your code cleaner and more readable:

// Good - Clear and concise
function Component({ name, age }) { }
 
// Avoid - More verbose
function Component(props) {
  const name = props.name;
  const age = props.age;
}

Use Meaningful Prop Names

Choose names that clearly describe what the prop represents:

// Good - Self-documenting
<UserCard userName="John" userEmail="john@example.com" />
 
// Avoid - Unclear
<UserCard a="John" b="john@example.com" />

Provide Default Values

Default values make your components more flexible and prevent errors:

function Button({ text = 'Click me', variant = 'primary' }) {
  // Component can work even if props aren't provided
}

Keep Props Simple

Avoid passing too many props to a single component. If you find yourself passing many props, consider:

  • Breaking the component into smaller pieces
  • Grouping related props into an object
  • Using composition instead

Don't Mutate Props

Props should be treated as read-only. Never modify props directly:

// Wrong - Don't do this!
function Component({ user }) {
  user.name = 'New Name'; // Never mutate props!
}
 
// Right - Create a new object if needed
function Component({ user }) {
  const updatedUser = { ...user, name: 'New Name' };
}

Common Patterns

Conditional Props

You can conditionally pass props:

function Button({ text, variant, disabled }) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
    >
      {text}
    </button>
  );
}
 
// Conditionally pass disabled prop
<Button 
  text="Submit" 
  variant="primary"
  {...(isLoading && { disabled: true })}
/>

Prop Drilling

Sometimes you need to pass props through multiple levels of components. This is called "prop drilling":

function App() {
  const user = { name: 'John', role: 'admin' };
  return <Layout user={user} />;
}
 
function Layout({ user }) {
  return <Header user={user} />;
}
 
function Header({ user }) {
  return <UserMenu user={user} />;
}

For deeply nested prop drilling, consider using Context API or state management libraries.


Conclusion

Props are essential to React development. They enable component reusability, make your code more maintainable, and create a clear data flow in your application. By mastering props, you'll be able to build flexible, reusable components that can adapt to different situations.

Remember, props flow downward from parent to child, they're read-only, and they're React's primary way of making components configurable and reusable.


Visual Explanation: Props Flow

Here's how props flow from parent to child:

Parent Component
┌─────────────────────────────┐
│ const user = {              │
│   name: "John",             │
│   age: 25                   │
│ }                           │
│                             │
│ <UserCard                   │
│   name={user.name}  ────────┼───┐
│   age={user.age}    ────────┼───┼───┐
│ />                          │   │   │
└─────────────────────────────┘   │   │
                                  │   │
                                  ▼   ▼
                         Child Component
                         ┌─────────────────┐
                         │ function UserCard│
                         │ ({ name, age }) │
                         │                 │
                         │ name = "John"   │
                         │ age = 25        │
                         │                 │
                         │ <h1>{name}</h1> │
                         │ <p>{age}</p>    │
                         └─────────────────┘

Key Points:

  • Props flow DOWN (parent → child)
  • Props are READ-ONLY (child can't change them)
  • Props are like function arguments

Props vs State Comparison

┌─────────────────┬─────────────────┐
│      PROPS      │      STATE      │
├─────────────────┼─────────────────┤
│ From parent     │ Internal        │
│ Read-only       │ Can change      │
│ Immutable       │ Mutable         │
│ Passed down     │ Managed locally │
│ Like arguments  │ Like variables  │
└─────────────────┴─────────────────┘

Frequently Asked Questions (FAQ)

Q1: Can I change props in a child component?

A: No! Props are read-only. If you need to change data, lift the state up to the parent component and pass down a function to update it.

Q2: What happens if I don't pass a required prop?

A: The prop will be undefined. Use default values or PropTypes/TypeScript to handle this:

function Component({ name = 'Guest' }) {
  return <div>Hello {name}</div>;
}

Q3: Can I pass functions as props?

A: Yes! Functions are commonly passed as props, especially for event handlers:

function Parent() {
  const handleClick = () => console.log('Clicked');
  return <Child onClick={handleClick} />;
}

Q4: How many props is too many?

A: If you're passing more than 5-7 props, consider:

  • Grouping related props into an object
  • Breaking the component into smaller pieces
  • Using Context API for deeply nested props

Q5: Can I pass components as props?

A: Yes! This is called "render props" pattern:

<Container render={(data) => <Display data={data} />} />

Q6: What's the difference between props and state?

A:

  • Props - Data from parent, read-only, can't change
  • State - Data managed by component, can change, triggers re-renders

Q7: Can I use props in the dependency array of useEffect?

A: Yes! If your effect uses props, include them:

useEffect(() => {
  fetchUser(userId); // userId is a prop
}, [userId]); // Include prop in dependencies

Q8: How do I pass props to multiple levels?

A: You can pass through intermediate components (prop drilling), or use Context API for deeply nested data. For 2-3 levels, prop drilling is fine. For more, consider Context.

Q9: Can props be optional?

A: Yes! Use default parameters:

function Component({ name, age = 0 }) {
  // age is optional, defaults to 0
}

Q10: What happens to props when parent re-renders?

A: Child components re-render when props change. React compares old and new props - if they're different, the child re-renders. If props are the same, React skips re-rendering (with React.memo).


Next Steps

Now that you understand props, here's what to explore next: