What are Components?

Components are independent, reusable pieces of code that return React elements to be rendered to the page.

Think of components as JavaScript functions: They accept inputs (called "props") and return React elements describing what should appear on the screen.

Component Types

Functional Components

Modern approach using functions and Hooks

  • Simpler syntax
  • Use Hooks for state and effects
  • Better performance
  • Recommended approach
Class Components

Traditional ES6 class approach

  • More verbose
  • Uses lifecycle methods
  • Legacy code support
  • Being phased out

Functional Components

Basic Functional Component

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

// Arrow function syntax
const Welcome = () => {
  return <h1>Hello, World!</h1>;
};

// Shorthand (implicit return)
const Welcome = () => <h1>Hello, World!</h1>;

Component with JSX

function UserProfile() {
  return (
    <div className="profile">
      <img src="avatar.jpg" alt="User" />
      <h2>John Doe</h2>
      <p>Software Developer</p>
    </div>
  );
}

export default UserProfile;

Exporting Components

// Default export
export default function Button() {
  return <button>Click me</button>;
}

// Named export
export function PrimaryButton() {
  return <button className="primary">Primary</button>;
}

export function SecondaryButton() {
  return <button className="secondary">Secondary</button>;
}

// Import examples
import Button from './Button';
import { PrimaryButton, SecondaryButton } from './Buttons';

Props (Properties)

Props are arguments passed to React components, similar to function parameters.

Basic Props

// Component with props
function Greeting({ name, age }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>You are {age} years old.</p>
    </div>
  );
}

// Usage
<Greeting name="Alice" age={25} />

Default Props

function Button({ text = "Click me", variant = "primary" }) {
  return <button className={`btn btn-${variant}`}>{text}</button>;
}

// Usage
<Button /> // Uses defaults
<Button text="Submit" variant="success" />

Props with Children

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

// Usage
<Card title="My Card">
  <p>This is the card content</p>
  <button>Action</button>
</Card>

Props Destructuring

// Without destructuring
function User(props) {
  return <p>{props.name} - {props.email}</p>;
}

// With destructuring (recommended)
function User({ name, email }) {
  return <p>{name} - {email}</p>;
}

// With rest operator
function User({ name, ...otherProps }) {
  return (
    <div {...otherProps}>
      <p>{name}</p>
    </div>
  );
}

State Management

State allows components to create and manage their own data.

useState Hook

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <button onClick={() => setCount(count - 1)}>
        Decrement
      </button>
      <button onClick={() => setCount(0)}>
        Reset
      </button>
    </div>
  );
}

Multiple State Variables

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsSubmitting(true);

    // API call here
    await submitForm({ name, email });

    setIsSubmitting(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <button disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}

Object State

function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });

  const handleChange = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    });
  };

  return (
    <form>
      <input
        name="name"
        value={user.name}
        onChange={handleChange}
      />
      <input
        name="email"
        value={user.email}
        onChange={handleChange}
      />
      <input
        name="age"
        type="number"
        value={user.age}
        onChange={handleChange}
      />
    </form>
  );
}

Component Lifecycle

Understanding component lifecycle with useEffect Hook.

useEffect Hook

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // Runs after every render
  useEffect(() => {
    console.log('Component rendered');
  });

  // Runs only once (on mount)
  useEffect(() => {
    fetchUser();
  }, []);

  // Runs when userId changes
  useEffect(() => {
    fetchUser();
  }, [userId]);

  // Cleanup function
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Tick');
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  const fetchUser = async () => {
    setLoading(true);
    const data = await fetch(`/api/users/${userId}`);
    setUser(await data.json());
    setLoading(false);
  };

  if (loading) return <p>Loading...</p>;

  return <div>{user.name}</div>;
}

Best Practices

Do's
  • Keep components small and focused
  • Use functional components with Hooks
  • Extract reusable logic into custom Hooks
  • Use PropTypes or TypeScript for type checking
  • Follow naming conventions (PascalCase)
  • Use composition over inheritance
  • Keep state as local as possible
Don'ts
  • Don't mutate state directly
  • Don't use indexes as keys in lists
  • Don't create components inside other components
  • Don't forget to add dependencies to useEffect
  • Don't use class components for new code
  • Don't prop drill excessively
  • Don't ignore console warnings

Component Structure Example

// Good component structure
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './TodoList.css';

function TodoList({ initialTodos = [] }) {
  const [todos, setTodos] = useState(initialTodos);

  useEffect(() => {
    // Side effects here
  }, []);

  const addTodo = (text) => {
    setTodos([...todos, { id: Date.now(), text, completed: false }]);
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  return (
    <div className="todo-list">
      {/* Component JSX */}
    </div>
  );
}

TodoList.propTypes = {
  initialTodos: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number,
    text: PropTypes.string,
    completed: PropTypes.bool
  }))
};

export default TodoList;