Skip to main content

Components & Props

Components are the building blocks of any React application. They let you split the UI into independent, reusable pieces, and think about each piece in isolation.

Component Mental Model

Think of components like LEGO blocks:
┌─────────────────────────────────────────────────────────────────┐
│                         App Component                           │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Header Component                      │   │
│  │  ┌──────────────┐  ┌──────────────────────────────────┐ │   │
│  │  │    Logo      │  │         Navigation               │ │   │
│  │  └──────────────┘  └──────────────────────────────────┘ │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                     Main Content                         │   │
│  │  ┌─────────────────┐  ┌─────────────────────────────┐   │   │
│  │  │   ProductCard   │  │        ProductCard          │   │   │
│  │  └─────────────────┘  └─────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Footer Component                      │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Functional Components

In modern React, we primarily use Functional Components. They are simply JavaScript functions that return JSX.
// Function Declaration
function Welcome() {
  return <h1>Hello, World</h1>;
}

// Arrow Function (more common)
const Welcome = () => {
  return <h1>Hello, World</h1>;
};

// Arrow Function with implicit return
const Welcome = () => <h1>Hello, World</h1>;

Component Naming Rules

Component names must start with a capital letter. React treats lowercase elements as HTML tags.
// ❌ Wrong - React treats this as an HTML tag
const welcome = () => <h1>Hi</h1>;
<welcome /> // Renders as <welcome></welcome>

// ✅ Correct
const Welcome = () => <h1>Hi</h1>;
<Welcome /> // Renders the component

Importing and Exporting Components

// Welcome.jsx - Named export
export const Welcome = () => <h1>Hello</h1>;

// Welcome.jsx - Default export
const Welcome = () => <h1>Hello</h1>;
export default Welcome;

// App.jsx - Importing
import Welcome from './Welcome';           // Default import
import { Welcome } from './Welcome';       // Named import
import { Welcome as Greeting } from './Welcome'; // Renamed import

Props (Properties)

Props are the way to pass data from parent to child components. Think of them as function arguments.

Passing Props

function App() {
  return (
    <div>
      <Welcome name="Sara" age={25} isOnline={true} />
      <Welcome name="Bob" age={30} isOnline={false} />
    </div>
  );
}

Receiving Props

The component receives all props as a single object.
// Method 1: Props object
function Welcome(props) {
  return (
    <h1>
      Hello, {props.name}. You are {props.age} years old.
      {props.isOnline && <span> 🟢</span>}
    </h1>
  );
}

// Method 2: Destructuring (recommended)
function Welcome({ name, age, isOnline }) {
  return (
    <h1>
      Hello, {name}. You are {age} years old.
      {isOnline && <span> 🟢</span>}
    </h1>
  );
}

Props Are Read-Only (Immutable)

Never modify props! Props are read-only. Attempting to change them violates React’s core principles.
// ❌ NEVER do this
function BadComponent(props) {
  props.name = 'Changed'; // ERROR!
  return <h1>{props.name}</h1>;
}

// ✅ If you need to modify data, use state (covered in next chapter)

Default Props

You can provide default values for props when they’re not passed.
// Method 1: Default parameters (recommended)
function Button({ color = 'blue', size = 'medium', text }) {
  return (
    <button 
      style={{ backgroundColor: color }}
      className={`btn-${size}`}
    >
      {text}
    </button>
  );
}

// Method 2: defaultProps (older approach)
function Button({ color, size, text }) {
  // ...
}
Button.defaultProps = {
  color: 'blue',
  size: 'medium'
};

Children Prop

children is a special prop that allows you to pass components or content between the opening and closing tags.
function Card({ children, title }) {
  return (
    <div className="card">
      <h2 className="card-title">{title}</h2>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

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

Practical Children Patterns

// Layout component
function PageLayout({ children }) {
  return (
    <div className="page">
      <Header />
      <main>{children}</main>
      <Footer />
    </div>
  );
}

// Modal component
function Modal({ children, isOpen, onClose }) {
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
}

// Usage
<Modal isOpen={showModal} onClose={() => setShowModal(false)}>
  <h2>Confirm Action</h2>
  <p>Are you sure you want to proceed?</p>
  <button onClick={handleConfirm}>Yes</button>
</Modal>

Prop Types (Runtime Validation)

PropTypes provide runtime type checking for your components.
npm install prop-types
import PropTypes from 'prop-types';

function UserCard({ name, age, email, isAdmin, avatar }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
      {isAdmin && <span className="badge">Admin</span>}
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  email: PropTypes.string.isRequired,
  isAdmin: PropTypes.bool,
  avatar: PropTypes.string
};

UserCard.defaultProps = {
  isAdmin: false,
  avatar: '/default-avatar.png'
};

Common PropTypes

PropTypeDescription
PropTypes.stringString value
PropTypes.numberNumber value
PropTypes.boolBoolean value
PropTypes.arrayArray
PropTypes.objectObject
PropTypes.funcFunction
PropTypes.nodeAnything renderable (string, number, element)
PropTypes.elementReact element
PropTypes.oneOf(['a', 'b'])One of specific values
PropTypes.arrayOf(PropTypes.number)Array of specific type
PropTypes.shape({ name: PropTypes.string })Object with specific shape
For production apps, consider using TypeScript instead of PropTypes. It provides compile-time checking and better IDE support.

Component Composition Patterns

1. Container and Presentational Components

Separate logic from presentation:
// Presentational Component (dumb) - only handles display
function UserList({ users, onSelect }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id} onClick={() => onSelect(user)}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

// Container Component (smart) - handles logic
function UserListContainer() {
  const [users, setUsers] = useState([]);
  const [selectedUser, setSelectedUser] = useState(null);

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(setUsers);
  }, []);

  return <UserList users={users} onSelect={setSelectedUser} />;
}

2. Compound Components

Components that work together to form a complete UI:
// Accordion compound component
function Accordion({ children }) {
  const [openIndex, setOpenIndex] = useState(null);
  
  return (
    <div className="accordion">
      {React.Children.map(children, (child, index) =>
        React.cloneElement(child, {
          isOpen: index === openIndex,
          onToggle: () => setOpenIndex(index === openIndex ? null : index)
        })
      )}
    </div>
  );
}

function AccordionItem({ title, children, isOpen, onToggle }) {
  return (
    <div className="accordion-item">
      <button onClick={onToggle} className="accordion-header">
        {title}
        <span>{isOpen ? '−' : '+'}</span>
      </button>
      {isOpen && <div className="accordion-content">{children}</div>}
    </div>
  );
}

// Usage
<Accordion>
  <AccordionItem title="Section 1">Content 1</AccordionItem>
  <AccordionItem title="Section 2">Content 2</AccordionItem>
  <AccordionItem title="Section 3">Content 3</AccordionItem>
</Accordion>

3. Render Props

Pass a function as a child to share logic:
function MouseTracker({ children }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);

  return children(position);
}

// Usage
<MouseTracker>
  {({ x, y }) => (
    <div>Mouse position: {x}, {y}</div>
  )}
</MouseTracker>

Spreading Props

You can spread all props to a child component:
function Button({ className, children, ...rest }) {
  return (
    <button className={`btn ${className}`} {...rest}>
      {children}
    </button>
  );
}

// All native button props (onClick, disabled, type, etc.) are passed through
<Button 
  className="primary" 
  onClick={handleClick} 
  disabled={isLoading}
  type="submit"
>
  Submit
</Button>

🎯 Practice Exercises

Build an Alert component that accepts:
  • type: ‘success’ | ‘warning’ | ‘error’
  • title: string
  • children: content
function Alert({ type = 'info', title, children }) {
  const styles = {
    success: { bg: '#d1fae5', border: '#10b981', color: '#065f46' },
    warning: { bg: '#fef3c7', border: '#f59e0b', color: '#92400e' },
    error: { bg: '#fee2e2', border: '#ef4444', color: '#991b1b' },
    info: { bg: '#dbeafe', border: '#3b82f6', color: '#1e40af' }
  };

  const style = styles[type];

  return (
    <div style={{
      backgroundColor: style.bg,
      borderLeft: `4px solid ${style.border}`,
      padding: '16px',
      marginBottom: '16px'
    }}>
      {title && <strong style={{ color: style.color }}>{title}</strong>}
      <p style={{ color: style.color, margin: title ? '8px 0 0' : 0 }}>
        {children}
      </p>
    </div>
  );
}

// Usage
<Alert type="success" title="Success!">
  Your changes have been saved.
</Alert>

<Alert type="error">
  Something went wrong. Please try again.
</Alert>
Create a flexible card system with Card, Card.Header, Card.Body, and Card.Footer:
function Card({ children, className = '' }) {
  return (
    <div className={`card ${className}`} style={{
      border: '1px solid #e5e7eb',
      borderRadius: '8px',
      overflow: 'hidden'
    }}>
      {children}
    </div>
  );
}

Card.Header = function CardHeader({ children }) {
  return (
    <div style={{ padding: '16px', borderBottom: '1px solid #e5e7eb' }}>
      {children}
    </div>
  );
};

Card.Body = function CardBody({ children }) {
  return <div style={{ padding: '16px' }}>{children}</div>;
};

Card.Footer = function CardFooter({ children }) {
  return (
    <div style={{ 
      padding: '16px', 
      borderTop: '1px solid #e5e7eb',
      backgroundColor: '#f9fafb'
    }}>
      {children}
    </div>
  );
};

// Usage
<Card>
  <Card.Header>
    <h3>Card Title</h3>
  </Card.Header>
  <Card.Body>
    <p>Card content goes here.</p>
  </Card.Body>
  <Card.Footer>
    <button>Action</button>
  </Card.Footer>
</Card>
Create an Avatar component that shows initials if no image is provided:
function Avatar({ src, name, size = 40 }) {
  const getInitials = (name) => {
    return name
      .split(' ')
      .map(word => word[0])
      .join('')
      .toUpperCase()
      .slice(0, 2);
  };

  const colors = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#8b5cf6'];
  const colorIndex = name.charCodeAt(0) % colors.length;

  const style = {
    width: size,
    height: size,
    borderRadius: '50%',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: size * 0.4,
    fontWeight: 'bold',
    color: 'white',
    backgroundColor: colors[colorIndex]
  };

  if (src) {
    return (
      <img 
        src={src} 
        alt={name} 
        style={{ ...style, objectFit: 'cover' }}
        onError={(e) => e.target.style.display = 'none'}
      />
    );
  }

  return <div style={style}>{getInitials(name)}</div>;
}

// Usage
<Avatar src="/user.jpg" name="John Doe" size={50} />
<Avatar name="Jane Smith" size={40} />

Summary

ConceptDescription
Functional ComponentsFunctions that return JSX
PropsArguments passed to components (read-only)
DestructuringExtract props for cleaner code
Default PropsFallback values when props aren’t provided
childrenSpecial prop for nested content
PropTypesRuntime type checking
CompositionBuilding complex UIs from simple components
Spreading PropsPass all props with {...props}

Next Steps

In the next chapter, you’ll learn about State & useState — how to make your components interactive and dynamic!