reactjs
/

React Props – Component Communication

Last Sync: Today

On this page

13
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

reactjs

React Props – Component Communication

What are Props?

Props (short for properties) are read-only data passed from parent to child components. They allow components to be dynamic and reusable. Props are immutable – components cannot modify their own props.

Basic Props Usage

React JSXRead-only
1
// Parent component passing props
function App() {
  return (
    <div>
      <Greeting name="Alice" age={25} />
      <Greeting name="Bob" age={30} isAdmin={true} />
    </div>
  );
}

// Child component receiving props
function Greeting(props) {
  return (
    <div>
      <h1>Hello, {props.name}!</h1>
      <p>Age: {props.age}</p>
      {props.isAdmin && <span>Admin User</span>}
    </div>
  );
}

// Destructuring props
function Greeting({ name, age, isAdmin = false }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Age: {age}</p>
      {isAdmin && <span>Admin User</span>}
    </div>
  );
}

Prop Types (Type Checking)

React JSXRead-only
1
// With PropTypes library
import PropTypes from 'prop-types';

function UserCard({ name, age, email, status, hobbies, address }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
      <p>Status: {status}</p>
      <p>Hobbies: {hobbies.join(', ')}</p>
      <p>Address: {address.street}, {address.city}</p>
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  email: PropTypes.string.isRequired,
  status: PropTypes.oneOf(['active', 'inactive', 'pending']),
  hobbies: PropTypes.arrayOf(PropTypes.string),
  address: PropTypes.shape({
    street: PropTypes.string,
    city: PropTypes.string,
    zip: PropTypes.number
  })
};

UserCard.defaultProps = {
  age: 18,
  status: 'active',
  hobbies: []
};

TypeScript with Props

React TSXRead-only
1
// TypeScript interface for props
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
  children?: React.ReactNode;
}

// Functional component with typed props
function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) {
  const baseStyles = 'px-4 py-2 rounded';
  const variantStyles = {
    primary: 'bg-blue-500 text-white',
    secondary: 'bg-gray-500 text-white',
    danger: 'bg-red-500 text-white'
  };

  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`${baseStyles} ${variantStyles[variant]}`}
    >
      {label}
    </button>
  );
}

// Usage
function App() {
  return (
    <div>
      <Button label="Click Me" onClick={() => console.log('clicked')} />
      <Button label="Delete" variant="danger" onClick={() => console.log('deleted')} />
    </div>
  );
}

Children Prop

React JSXRead-only
1
// Container component using children
function Card({ title, children }) {
  return (
    <div className="card">
      <div className="card-header">
        <h3>{title}</h3>
      </div>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// Usage with nested content
function App() {
  return (
    <Card title="Welcome">
      <p>This is the card content</p>
      <button>Click me</button>
    </Card>
  );
}

// Multiple children patterns
function Layout({ header, sidebar, content }) {
  return (
    <div>
      <header>{header}</header>
      <div className="container">
        <aside>{sidebar}</aside>
        <main>{content}</main>
      </div>
    </div>
  );
}

// Usage
<Layout
  header={<Header />}
  sidebar={<Sidebar />}
  content={<MainContent />}
/>

Render Props Pattern

React JSXRead-only
1
// Component that accepts a render function as prop
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  return (
    <div onMouseMove={handleMouseMove}>
      {render(position)}
    </div>
  );
}

// Usage
function App() {
  return (
    <MouseTracker
      render={({ x, y }) => (
        <p>Mouse position: {x}, {y}</p>
      )}
    />
  );
}

// Or using children as function
function DataFetcher({ url, children }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return children({ data, loading });
}

// Usage
<DataFetcher url="/api/users">
  {({ data, loading }) => (
    loading ? <p>Loading...</p> : <UserList users={data} />
  )}
</DataFetcher>

Props Drilling and Solutions

React JSXRead-only
1
// ❌ Props Drilling - Passing through many levels
function App() {
  const [user, setUser] = useState({ name: 'John' });
  return <GrandParent user={user} />;
}

function GrandParent({ user }) {
  return <Parent user={user} />;
}

function Parent({ user }) {
  return <Child user={user} />;
}

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

// ✅ Solution 1: Context API
const UserContext = createContext();

function App() {
  const [user, setUser] = useState({ name: 'John' });
  return (
    <UserContext.Provider value={user}>
      <GrandParent />
    </UserContext.Provider>
  );
}

function GrandParent() { return <Parent />; }
function Parent() { return <Child />; }
function Child() {
  const user = useContext(UserContext);
  return <p>{user.name}</p>;
}

// ✅ Solution 2: Component Composition
function App() {
  const [user, setUser] = useState({ name: 'John' });
  return <Layout user={user} />;
}

function Layout({ user }) {
  return (
    <div>
      <Sidebar />
      <Content user={user} /> {/* Only pass where needed */}
    </div>
  );
}

Spreading Props

React JSXRead-only
1
// Spread props (use with caution)
function Button({ type, children, ...rest }) {
  return (
    <button type={type} {...rest}>
      {children}
    </button>
  );
}

// Usage
<Button type="submit" onClick={() => {}} disabled={false} className="btn">
  Submit
</Button>

// Rest parameters for separating specific props
function Input({ label, error, ...inputProps }) {
  return (
    <div>
      <label>{label}</label>
      <input {...inputProps} />
      {error && <span className="error">{error}</span>}
    </div>
  );
}

// Usage
<Input
  label="Email"
  type="email"
  placeholder="Enter email"
  value={email}
  onChange={handleChange}
  error={emailError}
/>

Common Props Patterns

PatternDescriptionExample
Default PropsFallback valuesvariant = 'primary'
Required PropsMust be providedname: string.isRequired
Optional PropsMay be omittedage?: number
Children PropNested contentchildren: ReactNode
Render PropsFunction as childrender={(data) => ...}
Component CompositionPass componentsheader={<Header />}

Props Best Practices

  • Keep props few – Aim for 3-5 props per component
  • Use destructuring – Cleaner component code
  • Provide default values – For optional props
  • Type your props – TypeScript or PropTypes
  • Avoid spreading unknown props – Can cause unexpected behavior
  • Use meaningful names – Props should be self-documenting
  • Don't mutate props – Props are read-only
  • Use children for composition – More flexible than multiple props

Common Mistakes

React JSXRead-only
1
// ❌ Mutating props
function BadComponent({ user }) {
  user.name = 'New Name'; // DON'T DO THIS!
  return <p>{user.name}</p>;
}

// ✅ Props are read-only
function GoodComponent({ user, onUpdate }) {
  const handleUpdate = () => {
    onUpdate({ ...user, name: 'New Name' });
  };
  return <p>{user.name}</p>;
}

// ❌ Passing unnecessary props
function Parent() {
  const [data, setData] = useState({ a: 1, b: 2, c: 3 });
  return <Child a={data.a} b={data.b} c={data.c} />; // Too many
}

// ✅ Pass only what's needed
function Parent() {
  const [data, setData] = useState({ a: 1, b: 2, c: 3 });
  return <Child data={data} />; // Or pass specific needed props
}

// ❌ Not using default props
function Button({ size = 'medium', variant }) { // Good default
  return <button className={`btn-${size} btn-${variant}`}>Click</button>;
}

// ❌ Boolean props without clear meaning
<Button active={true} disabled={false} loading={false} />

// ✅ Better boolean props
<Button isActive highlight={false} isLoading={false} />

Props vs State

AspectPropsState
MutabilityImmutable (read-only)Mutable (via setter)
OwnershipParent componentComponent itself
PurposePass data/behaviorTrack changes
Changes trigger re-render?Yes (if parent changes)Yes
Default valuesCan have defaultsInitial values

Conclusion

Props are essential for React component communication. They make components reusable and predictable. Use TypeScript or PropTypes for type safety, children for composition, and lift state up when needed. Avoid prop drilling by using Context or component composition.

Try it yourself

import { useState } from 'react';

// Define prop types with defaults
function ProductCard({ name, price, image, onBuy, discount = 0 }) {
  const discountedPrice = price * (1 - discount / 100);
  
  return (
    <div style={{ border: '1px solid #ddd', padding: '1rem', borderRadius: '8px' }}>
      <img src={image} alt={name} style={{ width: '100%', height: 'auto' }} />
      <h3>{name}</h3>
      <p>Original: ${price}</p>
      {discount > 0 && <p style={{ color: 'green' }}>Discounted: ${discountedPrice.toFixed(2)} ({discount}% off)</p>}
      <button onClick={() => onBuy({ name, price: discountedPrice })}>Buy Now</button>
    </div>
  );
}

// Component using children prop
function ProductGrid({ children, title }) {
  return (
    <div>
      <h2>{title}</h2>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' }}>
        {children}
      </div>
    </div>
  );
}

// Main app
function App() {
  const [cart, setCart] = useState([]);
  
  const addToCart = (product) => {
    setCart([...cart, product]);
    alert(`Added ${product.name} to cart!`);
  };
  
  return (
    <div>
      <h1>Product Store</h1>
      <ProductGrid title="Featured Products">
        <ProductCard
          name="Laptop"
          price={999}
          image="https://via.placeholder.com/150"
          onBuy={addToCart}
          discount={10}
        />
        <ProductCard
          name="Mouse"
          price={29}
          image="https://via.placeholder.com/150"
          onBuy={addToCart}
        />
        <ProductCard
          name="Keyboard"
          price={79}
          image="https://via.placeholder.com/150"
          onBuy={addToCart}
          discount={15}
        />
      </ProductGrid>
      
      <div style={{ marginTop: '2rem' }}>
        <h3>Shopping Cart ({cart.length} items)</h3>
        <ul>
          {cart.map((item, idx) => (
            <li key={idx}>{item.name} - ${item.price.toFixed(2)}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default App;

Test Your Knowledge

Q1
of 4

Are props mutable?

A
Yes
B
No
C
Only in class components
D
Only with hooks
Q2
of 4

How do you access children passed to a component?

A
this.children
B
props.children
C
props.child
D
this.props.child
Q3
of 4

What is prop drilling?

A
Passing props down multiple levels
B
Mutating props
C
Creating new props
D
Deleting props
Q4
of 4

How to set a default value for a prop?

A
prop.default = value
B
defaultProps
C
destructuring: { prop = value }
D
Both B and C

Frequently Asked Questions

Can props be changed?

No, props are read-only. Components cannot modify their own props.

Props vs state?

Props are passed from parent and immutable; state is internal and mutable.

What is prop drilling?

Passing props through multiple intermediate components that don't need them.

How to set default props?

Use destructuring default values: function Button({ size = 'medium' })

Previous

react components

Next

react state

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.