Components and Props: Building Blocks of React

Creating Reusable UI Elements with Data Flow

Overview

Welcome to our exploration of Components and Props! Think of components as LEGO blocks - individual pieces that combine to create complex structures. Props are like the instructions that tell each block how to look and behave. Today, you'll learn how to create reusable components and pass data between them effectively.

What are Components?

Components are independent, reusable pieces of UI. Imagine building a house: instead of crafting everything from scratch, you use pre-made components like doors, windows, and walls. Similarly, in React, you build UIs by combining components.

graph TD A[App Component] --> B[Header] A --> C[Main Content] A --> D[Footer] B --> E[Logo] B --> F[Navigation] F --> G[NavLink] F --> H[NavLink] F --> I[NavLink] C --> J[Article] C --> K[Sidebar] J --> L[Title] J --> M[Content] J --> N[Author] K --> O[Widget] K --> P[Widget]

Types of Components

Function Components (Modern Approach)

Function components are like recipes - they take ingredients (props) and return a dish (UI):


// Simple function component
function Welcome(props) {
    return <h1>Hello, {props.name}!</h1>;
}

// Arrow function component
const Welcome = (props) => {
    return <h1>Hello, {props.name}!</h1>;
};

// Even simpler with implicit return
const Welcome = (props) => <h1>Hello, {props.name}!</h1>;

// Using the component
<Welcome name="Sarah" />
            

Class Components (Legacy Approach)

Class components are like kitchen appliances - more complex but with additional features:


class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}!</h1>;
    }
}

// Class components have additional features like:
// - Lifecycle methods
// - this binding
// - State management (before hooks)
            

Understanding Props

Props (short for properties) are how components talk to each other. Think of props as parameters passed to a function:

graph LR A[Parent Component] -->|Props| B[Child Component] B -->|Events| A style A fill:#f9f,stroke:#333,stroke-width:2px style B fill:#bbf,stroke:#333,stroke-width:2px

Basic Props Usage


// Passing props to a component
function App() {
    return (
        <div>
            <UserCard 
                name="John Doe"
                email="john@example.com"
                age={30}
                isActive={true}
            />
        </div>
    );
}

// Receiving props in a component
function UserCard(props) {
    return (
        <div className="user-card">
            <h2>{props.name}</h2>
            <p>Email: {props.email}</p>
            <p>Age: {props.age}</p>
            <p>Status: {props.isActive ? 'Active' : 'Inactive'}</p>
        </div>
    );
}
            

Props Destructuring

Destructuring makes props cleaner and more readable:


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

// With destructuring
function UserCard({ name, email }) {
    return (
        <div>
            <h2>{name}</h2>
            <p>{email}</p>
        </div>
    );
}

// Destructuring with default values
function Button({ 
    text = 'Click me', 
    color = 'blue', 
    size = 'medium' 
}) {
    return (
        <button className={`btn btn-${color} btn-${size}`}>
            {text}
        </button>
    );
}
            

Props Are Read-Only

Props are immutable - like ingredients that can't be changed once passed to a recipe:


// ❌ WRONG - Never modify props
function Wrong(props) {
    props.name = 'New Name'; // This will cause an error!
    return <h1>{props.name}</h1>;
}

// ✅ CORRECT - Props are read-only
function Correct(props) {
    const displayName = props.name.toUpperCase();
    return <h1>{displayName}</h1>;
}
            

Children Props

The special "children" prop allows components to be nested like Russian dolls:


// Component that uses children
function Card({ children, title }) {
    return (
        <div className="card">
            <div className="card-header">
                <h3>{title}</h3>
            </div>
            <div className="card-body">
                {children}
            </div>
        </div>
    );
}

// Using the Card component
function App() {
    return (
        <Card title="User Profile">
            <p>Name: John Doe</p>
            <p>Email: john@example.com</p>
            <button>Edit Profile</button>
        </Card>
    );
}
            

Passing Different Types of Props

1. String Props


<Greeting message="Hello World" />
<Greeting message={'Hello World'} /> {/* Same as above */}
            

2. Number Props


<Counter start={0} />
<Counter start={42} />
            

3. Boolean Props


<Button disabled={true} />
<Button disabled /> {/* Shorthand for true */}
<Button disabled={false} />
            

4. Array Props


<List items={['Apple', 'Banana', 'Orange']} />
<List items={[1, 2, 3, 4, 5]} />
            

5. Object Props


<UserProfile 
    user={{
        name: 'John',
        age: 30,
        email: 'john@example.com'
    }} 
/>
            

6. Function Props (Callbacks)


<Button onClick={() => alert('Clicked!')} />
<Button onClick={handleClick} />
            

Component Composition vs Inheritance

React favors composition over inheritance. It's like cooking: instead of creating complex inheritance hierarchies, you combine simple ingredients:


// Composition example
function WelcomeDialog() {
    return (
        <Dialog 
            title="Welcome"
            message="Thank you for visiting our site!"
        />
    );
}

function Dialog({ title, message }) {
    return (
        <div className="dialog">
            <h1>{title}</h1>
            <p>{message}</p>
        </div>
    );
}

// More complex composition
function SignUpDialog() {
    return (
        <Dialog title="Sign Up">
            <input type="email" placeholder="Email" />
            <input type="password" placeholder="Password" />
            <button>Sign Up</button>
        </Dialog>
    );
}
            

Props Best Practices

1. Use PropTypes for Type Checking


import PropTypes from 'prop-types';

function UserCard({ name, age, email }) {
    return (
        <div>
            <h2>{name}</h2>
            <p>Age: {age}</p>
            <p>Email: {email}</p>
        </div>
    );
}

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

2. Use Default Props


function Button({ text, color, size }) {
    return (
        <button className={`btn btn-${color} btn-${size}`}>
            {text}
        </button>
    );
}

Button.defaultProps = {
    text: 'Click me',
    color: 'blue',
    size: 'medium'
};
            

Component Patterns

1. Container and Presentational Components


// Container Component (handles logic)
function UserListContainer() {
    const [users, setUsers] = useState([]);
    
    useEffect(() => {
        fetchUsers().then(setUsers);
    }, []);
    
    return <UserList users={users} />;
}

// Presentational Component (handles UI)
function UserList({ users }) {
    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}
            

2. Compound Components


// Compound components work together
function Tabs({ children }) {
    const [activeTab, setActiveTab] = useState(0);
    
    return (
        <div className="tabs">
            {React.Children.map(children, (child, index) => {
                return React.cloneElement(child, {
                    isActive: index === activeTab,
                    onActivate: () => setActiveTab(index)
                });
            })}
        </div>
    );
}

function Tab({ isActive, onActivate, children }) {
    return (
        <div 
            className={`tab ${isActive ? 'active' : ''}`}
            onClick={onActivate}
        >
            {children}
        </div>
    );
}

// Usage
<Tabs>
    <Tab>Home</Tab>
    <Tab>About</Tab>
    <Tab>Contact</Tab>
</Tabs>
            

Real-World Component Examples

1. Product Card Component


function ProductCard({ product, onAddToCart }) {
    const { name, price, image, description, inStock } = product;
    
    return (
        <div className="product-card">
            <img src={image} alt={name} />
            <h3>{name}</h3>
            <p>{description}</p>
            <div className="price">${price}</div>
            <button 
                onClick={() => onAddToCart(product)}
                disabled={!inStock}
            >
                {inStock ? 'Add to Cart' : 'Out of Stock'}
            </button>
        </div>
    );
}
            

2. Navigation Component


function Navigation({ links, currentPath }) {
    return (
        <nav>
            <ul>
                {links.map(link => (
                    <li key={link.path}>
                        <a 
                            href={link.path}
                            className={currentPath === link.path ? 'active' : ''}
                        >
                            {link.label}
                        </a>
                    </li>
                ))}
            </ul>
        </nav>
    );
}

// Usage
<Navigation 
    links={[
        { path: '/', label: 'Home' },
        { path: '/about', label: 'About' },
        { path: '/contact', label: 'Contact' }
    ]}
    currentPath="/"
/>
            

Component Communication Patterns

graph TD A[Parent Component] -->|Props| B[Child A] A -->|Props| C[Child B] B -->|Callback Props| A C -->|Callback Props| A A -->|Shared State| B A -->|Shared State| C

// Parent managing shared state
function ShoppingCart() {
    const [items, setItems] = useState([]);
    
    const addItem = (item) => {
        setItems([...items, item]);
    };
    
    const removeItem = (itemId) => {
        setItems(items.filter(item => item.id !== itemId));
    };
    
    return (
        <div>
            <ProductList onAddToCart={addItem} />
            <CartSummary 
                items={items} 
                onRemoveItem={removeItem} 
            />
        </div>
    );
}
            

Component Lifecycle and Props


function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
        // Fetch new user data when userId changes
        setLoading(true);
        fetchUser(userId)
            .then(userData => {
                setUser(userData);
                setLoading(false);
            });
    }, [userId]); // Re-run effect when userId changes
    
    if (loading) return <div>Loading...</div>;
    if (!user) return <div>User not found</div>;
    
    return (
        <div>
            <h2>{user.name}</h2>
            <p>{user.email}</p>
        </div>
    );
}
            

Practice Exercises

Exercise 1: Create a Blog Post Component

Create a BlogPost component that accepts title, author, date, and content props:


function BlogPost(/* your props here */) {
    // Display a blog post with:
    // - Title (h2)
    // - Author name
    // - Publication date
    // - Post content
    // - "Read more" button (optional)
}

// Usage:
<BlogPost 
    title="Getting Started with React"
    author="Jane Doe"
    date="2024-03-15"
    content="React is a powerful library..."
/>
            

Exercise 2: Build a Product Grid

Create a ProductGrid component that renders a grid of ProductCard components:


function ProductGrid({ products }) {
    // Render a grid of ProductCard components
    // Handle the case when products array is empty
}

function ProductCard({ product }) {
    // Display product information
    // Include an "Add to Cart" button
}
            

Exercise 3: Create a Tabbed Interface

Build a Tabs component that can display different content panels:


function Tabs({ tabs }) {
    // tabs is an array of { title: string, content: ReactNode }
    // Show tab titles as buttons
    // Display content of active tab
    // Allow switching between tabs
}

// Usage:
<Tabs 
    tabs={[
        { title: 'Description', content: <p>Product description...</p> },
        { title: 'Reviews', content: <Reviews /> },
        { title: 'Specifications', content: <Specs /> }
    ]}
/>
            

Key Takeaways

What's Next?

Tomorrow, we'll explore State and Lifecycle methods, learning how to make our components dynamic and interactive. We'll see how components can manage their own data and respond to user actions!

Homework