What are React Components?
Components are the building blocks of React applications. Think of them as custom, reusable pieces of UI that you can use throughout your application. Just like functions in JavaScript break down complex logic into smaller pieces, components break down complex UIs into manageable, independent parts.
Component Definition: A React component is a JavaScript function (or class) that returns JSX describing what should appear on the screen. Components can accept inputs (called "props") and maintain their own internal state.
There are two types of components in React:
- Function Components: JavaScript functions that return JSX (modern approach, focus of this lesson)
- Class Components: ES6 classes that extend React.Component (legacy approach, still used in older codebases)
Modern React development primarily uses function components with Hooks. This lesson focuses exclusively on function components.
Creating Your First Function Component
A function component is simply a JavaScript function that returns JSX. Here's the simplest possible component:
// Basic function component
function Welcome() {
return <h1>Hello, World!</h1>;
}
// Using arrow function (alternative syntax)
const Welcome = () => {
return <h1>Hello, World!</h1>;
};
// Implicit return with arrow function (for simple JSX)
const Welcome = () => <h1>Hello, World!</h1>;
Naming Convention: Component names MUST start with a capital letter. This tells React that <Welcome /> is a component, not an HTML tag. Use PascalCase for component names: UserProfile, NavigationBar, ProductCard.
Let's create a more realistic component:
function UserCard() {
const name = 'Sarah Johnson';
const role = 'Frontend Developer';
const avatar = 'https://via.placeholder.com/150';
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{role}</p>
<button>View Profile</button>
</div>
);
}
Using Components
Once you've defined a component, you can use it just like an HTML element in JSX:
function App() {
return (
<div className="App">
<UserCard />
<UserCard />
<UserCard />
</div>
);
}
This will render three identical user cards. But wait - what if we want each card to display different information? That's where props come in (we'll cover props basics shortly)!
Component Syntax: Components can be written as self-closing tags <UserCard /> or with opening and closing tags <UserCard></UserCard>. For components without children, self-closing is preferred.
Component Composition
One of React's most powerful features is component composition - building complex UIs by combining smaller components:
// Small, focused components
function Avatar({ src, alt }) {
return <img src={src} alt={alt} className="avatar" />;
}
function UserName({ name }) {
return <h2 className="user-name">{name}</h2>;
}
function UserRole({ role }) {
return <p className="user-role">{role}</p>;
}
// Composed component using smaller components
function UserCard({ name, role, avatar }) {
return (
<div className="user-card">
<Avatar src={avatar} alt={name} />
<UserName name={name} />
<UserRole role={role} />
<button>View Profile</button>
</div>
);
}
Benefits of Component Composition:
- Reusability:
Avatar component can be used anywhere you need to display a user image
- Maintainability: Changes to avatar styling only happen in one place
- Testability: Small components are easier to test in isolation
- Readability: The composed component clearly shows its structure
Introduction to Props
Props (short for "properties") are how you pass data from a parent component to a child component. They make components dynamic and reusable.
// Component that accepts props
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// Using the component with different props
function App() {
return (
<div>
<Greeting name="Alice" />
<Greeting name="Bob" />
<Greeting name="Charlie" />
</div>
);
}
The output will be three different greetings: "Hello, Alice!", "Hello, Bob!", and "Hello, Charlie!"
Props are Read-Only: A component must never modify its own props. Props flow in one direction: from parent to child. This is called "unidirectional data flow" and it makes React apps predictable and easier to debug.
Destructuring Props
Instead of accessing props with props.name, you can destructure them for cleaner code:
// Without destructuring
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// With destructuring (preferred)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Multiple props
function UserCard({ name, role, avatar, isOnline }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{role}</p>
{isOnline && <span className="status">Online</span>}
</div>
);
}
Destructuring makes your component code more concise and easier to read. It's the preferred approach in modern React development.
Default Props
You can provide default values for props in case they're not passed by the parent:
// Method 1: Default parameters (ES6)
function Button({ text = 'Click me', type = 'button' }) {
return <button type={type}>{text}</button>;
}
// Method 2: Using logical OR operator
function Avatar({ src, size }) {
const avatarSize = size || 100;
return (
<img
src={src}
width={avatarSize}
height={avatarSize}
alt="User avatar"
/>
);
}
// Usage
function App() {
return (
<div>
<Button /> {/* Uses default "Click me" */}
<Button text="Submit" /> {/* Uses "Submit" */}
<Avatar src="photo.jpg" /> {/* Uses default size 100 */}
<Avatar src="photo.jpg" size={200} /> {/* Uses size 200 */}
</div>
);
}
Default Parameters vs. defaultProps: For function components, use ES6 default parameters in the function signature. The older Component.defaultProps syntax is mainly for class components (though it also works with function components).
Component Organization and File Structure
As your application grows, organizing components becomes crucial:
Option 1: Single File per Component
src/
├── components/
│ ├── Button.js
│ ├── UserCard.js
│ ├── Avatar.js
│ └── Navigation.js
└── App.js
Option 2: Component Folders
src/
├── components/
│ ├── Button/
│ │ ├── Button.js
│ │ ├── Button.css
│ │ └── Button.test.js
│ ├── UserCard/
│ │ ├── UserCard.js
│ │ ├── UserCard.css
│ │ └── index.js
│ └── Avatar/
│ ├── Avatar.js
│ └── Avatar.css
└── App.js
Best Practices for Component Files:
// UserCard.js - Component file structure
// 1. Imports at the top
import React from 'react';
import './UserCard.css';
import Avatar from './Avatar';
// 2. Component definition
function UserCard({ name, role, avatar, onViewProfile }) {
// 3. Component logic (if any)
const handleClick = () => {
console.log(`Viewing profile: ${name}`);
onViewProfile(name);
};
// 4. Return JSX
return (
<div className="user-card">
<Avatar src={avatar} alt={name} />
<h2>{name}</h2>
<p>{role}</p>
<button onClick={handleClick}>View Profile</button>
</div>
);
}
// 5. Export at the bottom
export default UserCard;
Importing and Exporting Components
Named Exports:
// Components.js - Multiple components in one file
export function Button({ text }) {
return <button>{text}</button>;
}
export function Input({ placeholder }) {
return <input placeholder={placeholder} />;
}
// Importing named exports
import { Button, Input } from './Components';
Default Exports:
// UserCard.js
function UserCard({ name, role }) {
return (
<div>
<h2>{name}</h2>
<p>{role}</p>
</div>
);
}
export default UserCard;
// Importing default export (can rename during import)
import UserCard from './UserCard';
import Card from './UserCard'; // Valid: can use any name
One Default Export Per File: Each file can only have one default export, but can have multiple named exports. Use default exports for the main component of a file, and named exports for helper components or utilities.
Practical Component Examples
Example 1: Product Card Component
function ProductCard({ name, price, image, inStock, onAddToCart }) {
return (
<div className="product-card">
<img src={image} alt={name} />
<h3>{name}</h3>
<p className="price">${price.toFixed(2)}</p>
{inStock ? (
<button onClick={onAddToCart}>Add to Cart</button>
) : (
<p className="out-of-stock">Out of Stock</p>
)}
</div>
);
}
// Usage
function Shop() {
const handleAddToCart = (productName) => {
console.log(`Added ${productName} to cart`);
};
return (
<div className="shop">
<ProductCard
name="Wireless Headphones"
price={79.99}
image="headphones.jpg"
inStock={true}
onAddToCart={() => handleAddToCart('Wireless Headphones')}
/>
</div>
);
}
Example 2: Alert Component with Variants
function Alert({ type = 'info', message, onClose }) {
const getClassName = () => {
return `alert alert-${type}`;
};
const getIcon = () => {
switch(type) {
case 'success': return '✓';
case 'error': return '✗';
case 'warning': return '!';
default: return 'i';
}
};
return (
<div className={getClassName()}>
<span className="icon">{getIcon()}</span>
<p>{message}</p>
{onClose && <button onClick={onClose}>×</button>}
</div>
);
}
// Usage
function App() {
return (
<div>
<Alert type="success" message="Profile updated successfully!" />
<Alert type="error" message="Failed to save changes" />
<Alert type="warning" message="Your session will expire soon" />
</div>
);
}
Example 3: Loading Spinner Component
function LoadingSpinner({ size = 'medium', text = 'Loading...' }) {
const sizeClasses = {
small: 'spinner-small',
medium: 'spinner-medium',
large: 'spinner-large'
};
return (
<div className="loading-container">
<div className={`spinner ${sizeClasses[size]}`}></div>
{text && <p>{text}</p>}
</div>
);
}
// Usage
function Dashboard() {
const isLoading = true;
return (
<div>
{isLoading ? (
<LoadingSpinner size="large" text="Loading dashboard..." />
) : (
<div>Dashboard content here</div>
)}
</div>
);
}
Exercise 1: Create a ProfileCard Component
Build a ProfileCard component that displays user profile information:
Requirements:
- Accept props: name, bio, avatar, skills (array), isVerified (boolean)
- Display avatar image, name, and bio
- Show a "Verified ✓" badge if isVerified is true
- List all skills as pills/badges
- Include a "Connect" button
- Use default values for avatar and bio if not provided
Test data:
const user = {
name: 'Emily Chen',
bio: 'Full-stack developer passionate about React and Node.js',
avatar: 'https://via.placeholder.com/150',
skills: ['React', 'JavaScript', 'Node.js', 'MongoDB'],
isVerified: true
};
Exercise 2: Build a Card Layout System
Create a flexible card component system with composition:
Components to create:
Card - Main container with border and shadow
CardHeader - Header section with title
CardBody - Content area
CardFooter - Footer with actions
Usage should look like:
<Card>
<CardHeader title="Welcome" />
<CardBody>
<p>This is the card content.</p>
</CardBody>
<CardFooter>
<button>Learn More</button>
</CardFooter>
</Card>
Exercise 3: Create a StatusBadge Component
Build a reusable status badge component:
Requirements:
- Accept props: status (string), size (small/medium/large)
- Support statuses: "active", "pending", "completed", "cancelled"
- Each status should have a different color (green, yellow, blue, red)
- Add appropriate icons or symbols for each status
- Make it work with different sizes
Usage examples:
<StatusBadge status="active" size="small" />
<StatusBadge status="pending" />
<StatusBadge status="completed" size="large" />
Common Component Patterns
Pattern 1: Container Components
Components that manage state and logic, passing data to presentational components:
// Container component (manages data)
function UserListContainer() {
const users = [
{ id: 1, name: 'Alice', role: 'Developer' },
{ id: 2, name: 'Bob', role: 'Designer' }
];
return <UserList users={users} />;
}
// Presentational component (displays data)
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
}
Pattern 2: Conditional Wrappers
function ConditionalWrapper({ condition, wrapper, children }) {
return condition ? wrapper(children) : children;
}
// Usage
<ConditionalWrapper
condition={isHighlighted}
wrapper={children => <div className="highlight">{children}</div>}
>
<p>This content may be highlighted</p>
</ConditionalWrapper>
Summary
In this lesson, you learned:
- Components are reusable, independent pieces of UI
- Function components are JavaScript functions that return JSX
- Component names must start with a capital letter (PascalCase)
- Component composition builds complex UIs from smaller components
- Props pass data from parent to child components
- Props are read-only and flow unidirectionally
- Destructuring props makes component code cleaner
- Default props provide fallback values
- Proper component organization improves maintainability
- Named and default exports control how components are shared
In the next lesson, we'll dive deeper into props, exploring advanced prop patterns, prop validation, the children prop, and how to handle complex data flow between components!