React Components and Props

React Components and Props

React is a popular JavaScript library for building user interfaces. This tutorial covers the fundamentals of React components and props.

What are Components?

Components are the building blocks of React applications. They are reusable pieces of code that return HTML elements to be rendered on the page.

Types of Components

1. Function Components

Modern React applications primarily use function components.

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

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

// Arrow function component
const Greeting = () => {
  return <p>Welcome to React!</p>;
};

// Arrow function with props
const UserCard = (props) => {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>{props.email}</p>
    </div>
  );
};

2. Class Components

Older React applications use class components (still supported but less common).

import React from 'react';

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

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

Understanding Props

Props (short for “properties”) are read-only data passed from parent to child components.

Basic Props Usage

// Parent component
function App() {
  return (
    <div>
      <Welcome name="Alice" />
      <Welcome name="Bob" />
      <Welcome name="Charlie" />
    </div>
  );
}

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

Destructuring Props

// Traditional props access
function UserProfile(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>{props.email}</p>
      <p>Age: {props.age}</p>
    </div>
  );
}

// With destructuring
function UserProfile({ name, email, age }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
      <p>Age: {age}</p>
    </div>
  );
}

// With default values
function Button({ text = "Click me", color = "blue" }) {
  return <button style={{ backgroundColor: color }}>{text}</button>;
}

Props with Different Data Types

function ProductCard({ product }) {
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <p>In stock: {product.inStock ? "Yes" : "No"}</p>
      <button onClick={product.onAddToCart}>
        Add to Cart
      </button>
    </div>
  );
}

// Usage
function App() {
  const handleAddToCart = () => {
    console.log("Product added to cart!");
  };

  const product = {
    name: "Wireless Headphones",
    price: 79.99,
    image: "/headphones.jpg",
    inStock: true,
    onAddToCart: handleAddToCart
  };

  return <ProductCard product={product} />;
}

Component Composition

Components can be composed together to build complex UIs.

// Card component
function Card({ children, title }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-content">
        {children}
      </div>
    </div>
  );
}

// List component
function List({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

// Using composition
function App() {
  const fruits = ["Apple", "Banana", "Orange"];
  
  return (
    <div>
      <Card title="Fruit List">
        <List items={fruits} />
      </Card>
      
      <Card title="Welcome">
        <p>This is a card with custom content.</p>
        <button>Click me</button>
      </Card>
    </div>
  );
}

Conditional Rendering

Components can render different content based on conditions.

function UserGreeting({ isLoggedIn, username }) {
  if (isLoggedIn) {
    return <h1>Welcome back, {username}!</h1>;
  }
  return <h1>Please sign in.</h1>;
}

// Using ternary operator
function StatusMessage({ status }) {
  return (
    <div>
      {status === "loading" ? (
        <p>Loading...</p>
      ) : status === "success" ? (
        <p>Success!</p>
      ) : (
        <p>Error occurred.</p>
      )}
    </div>
  );
}

// Using logical AND
function Notification({ message }) {
  return (
    <div>
      {message && (
        <div className="notification">
          {message}
        </div>
      )}
    </div>
  );
}

Lists and Keys

When rendering lists, each item needs a unique key.

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <span 
            style={{ 
              textDecoration: todo.completed ? 'line-through' : 'none' 
            }}
          >
            {todo.text}
          </span>
        </li>
      ))}
    </ul>
  );
}

// Usage
function App() {
  const todos = [
    { id: 1, text: "Learn React", completed: false },
    { id: 2, text: "Build a project", completed: true },
    { id: 3, text: "Deploy to production", completed: false }
  ];

  return <TodoList todos={todos} />;
}

Props Validation

Use PropTypes to validate props (requires installing prop-types package).

import PropTypes from 'prop-types';

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

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

// Default props
UserCard.defaultProps = {
  age: 0
};

Children Prop

The children prop allows you to pass components or elements directly between opening and closing tags.

function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// Usage
function App() {
  return (
    <Card title="Profile">
      <img src="/avatar.jpg" alt="Avatar" />
      <h3>John Doe</h3>
      <p>Web Developer</p>
      <button>Contact</button>
    </Card>
  );
}

Best Practices

  1. Keep components small: Focus on single responsibility
  2. Use descriptive names: Component names should describe what they do
  3. Destructure props: Makes code cleaner and more readable
  4. Use PropTypes: Validate props to catch bugs early
  5. Add keys to lists: Always provide unique keys when rendering lists
// Good example
const ProductCard = ({ name, price, image, onAddToCart }) => {
  return (
    <div className="product-card">
      <img src={image} alt={name} />
      <h3>{name}</h3>
      <p>${price}</p>
      <button onClick={onAddToCart}>Add to Cart</button>
    </div>
  );
};

ProductCard.propTypes = {
  name: PropTypes.string.isRequired,
  price: PropTypes.number.isRequired,
  image: PropTypes.string.isRequired,
  onAddToCart: PropTypes.func.isRequired
};

Complete Example

import React from 'react';
import PropTypes from 'prop-types';

// Button component
const Button = ({ children, onClick, variant = "primary" }) => {
  const className = `btn btn-${variant}`;
  return (
    <button className={className} onClick={onClick}>
      {children}
    </button>
  );
};

Button.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func,
  variant: PropTypes.oneOf(['primary', 'secondary', 'danger'])
};

// UserCard component
const UserCard = ({ user, onFollow, onMessage }) => {
  return (
    <div className="user-card">
      <img src={user.avatar} alt={user.name} />
      <div className="user-info">
        <h3>{user.name}</h3>
        <p>@{user.username}</p>
        <p>{user.bio}</p>
        <div className="user-stats">
          <span>{user.followers} followers</span>
          <span>{user.following} following</span>
        </div>
        <div className="user-actions">
          <Button onClick={onFollow}>Follow</Button>
          <Button variant="secondary" onClick={onMessage}>
            Message
          </Button>
        </div>
      </div>
    </div>
  );
};

UserCard.propTypes = {
  user: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    username: PropTypes.string.isRequired,
    avatar: PropTypes.string.isRequired,
    bio: PropTypes.string,
    followers: PropTypes.number,
    following: PropTypes.number
  }).isRequired,
  onFollow: PropTypes.func.isRequired,
  onMessage: PropTypes.func.isRequired
};

// App component
function App() {
  const handleFollow = (userId) => {
    console.log(`Following user ${userId}`);
  };

  const handleMessage = (userId) => {
    console.log(`Messaging user ${userId}`);
  };

  const user = {
    id: 1,
    name: "Jane Smith",
    username: "janesmith",
    avatar: "/jane-avatar.jpg",
    bio: "Frontend developer passionate about React",
    followers: 1234,
    following: 567
  };

  return (
    <div className="app">
      <UserCard 
        user={user}
        onFollow={() => handleFollow(user.id)}
        onMessage={() => handleMessage(user.id)}
      />
    </div>
  );
}

export default App;

External Resources:

Related Tutorials:

Last updated on