The Foundation of React Components
Imagine you're building with LEGO blocks. Each block can be different colors and sizes, but they all connect in predictable ways. React components work similarly - they're reusable pieces that can accept different "props" (properties) but work together in consistent ways to build your application.
Let's start with a simple component and build our understanding from there:
// A basic greeting component
function Greeting({ name }) {
return (
<div className="greeting">
Hello, {name}!
</div>
);
}
// Using the component
<Greeting name="Alice" /> // Displays: Hello, Alice!
<Greeting name="Bob" /> // Displays: Hello, Bob!
Providing Default Values for Props
Just as a restaurant might have default sides that come with a meal unless specified otherwise, React components can have default prop values. This makes components more resilient and user-friendly. Let's explore different ways to set defaults:
// Method 1: Using defaultProps
function UserCard({ name, role, avatar }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{role}</p>
</div>
);
}
UserCard.defaultProps = {
role: 'Member',
avatar: '/default-avatar.png'
};
// Method 2: Using default parameters in destructuring
function UserCard({
name,
role = 'Member',
avatar = '/default-avatar.png'
}) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{role}</p>
</div>
);
}
// Both methods allow these usages:
<UserCard name="Alice" /> // Uses default role and avatar
<UserCard
name="Bob"
role="Admin"
avatar="/bob.png"
/> // Uses provided values
Default props are especially useful when:
1. You have commonly used values that should apply in most cases
2. You want to make certain props optional
3. You need to ensure your component doesn't break if a prop is omitted
Passing Props Into Components
Think of props like passing arguments to a function - they allow you to customize how a component behaves or appears. Let's explore various ways to pass props:
// A more complex example showing different ways to pass props
function ProductCard({
title,
price,
onAddToCart,
isOnSale,
salePrice,
category
}) {
return (
<div className={`product ${isOnSale ? 'on-sale' : ''}`}>
<h3>{title}</h3>
<p className="category">{category}</p>
<div className="price-section">
{isOnSale ? (
<>
<span className="original-price">
${price}
</span>
<span className="sale-price">
${salePrice}
</span>
</>
) : (
<span className="regular-price">
${price}
</span>
)}
</div>
<button onClick={onAddToCart}>
Add to Cart
</button>
</div>
);
}
// Using the component with different prop combinations
function Shop() {
const handleAddToCart = (productId) => {
console.log(`Adding product ${productId} to cart`);
};
return (
<div className="shop">
{/* Regular product */}
<ProductCard
title="Basic T-Shirt"
price={19.99}
category="Clothing"
onAddToCart={() => handleAddToCart(1)}
isOnSale={false}
/>
{/* Sale product */}
<ProductCard
title="Designer Jeans"
price={89.99}
salePrice={59.99}
category="Clothing"
onAddToCart={() => handleAddToCart(2)}
isOnSale={true}
/>
</div>
);
}
Debugging Component Renders
Understanding when and why your components render is crucial for building efficient React applications. Let's explore some debugging techniques:
// Using console.log for basic debugging
function DebugComponent({ value }) {
console.log('DebugComponent rendering with value:', value);
return <div>{value}</div>;
}
// Using React Developer Tools
function DebuggableComponent({ data, onUpdate }) {
// Custom hook for debugging renders
useDebugRender('DebuggableComponent', { data, onUpdate });
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={onUpdate}>Update</button>
</div>
);
}
// Custom hook for debugging renders
function useDebugRender(componentName, props) {
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
console.log(
`${componentName} rendered ${renderCount.current} times`,
'Props:', props
);
});
}
Key debugging tips:
1. Use React Developer Tools to inspect component props and state
2. Add console.log statements strategically
3. Create custom debugging hooks for reusable debugging logic
4. Pay attention to unnecessary re-renders
Destructuring Props
Destructuring props makes your code cleaner and more readable, like unpacking a box and laying out all its contents neatly. Let's explore different destructuring patterns:
// Basic destructuring
function UserProfile({ name, email, avatar }) {
return (
<div className="profile">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
// Nested destructuring
function ComplexProfile({
user: { name, email },
settings: { theme, notifications }
}) {
return (
<div className={`profile theme-${theme}`}>
<h2>{name}</h2>
<p>{email}</p>
<div className="settings">
Notifications: {notifications ? 'On' : 'Off'}
</div>
</div>
);
}
// Destructuring with rest parameters
function Card({ title, className, ...rest }) {
return (
<div className={`card ${className}`} {...rest}>
<h3>{title}</h3>
</div>
);
}
// Destructuring in the parameter list vs. in the function body
function ProductDisplay(props) {
// Destructuring in the function body
const {
product: { name, price, description },
showDescription = true,
onBuy
} = props;
return (
<div className="product">
<h2>{name}</h2>
<p className="price">${price}</p>
{showDescription && (
<p className="description">{description}</p>
)}
<button onClick={onBuy}>Buy Now</button>
</div>
);
}
Putting It All Together
Let's create a complete example that combines all these concepts into a practical component:
// A complete example showing all concepts
function Dashboard({
user = { name: 'Guest', role: 'Visitor' },
theme = 'light',
onThemeToggle,
tasks = [],
...otherProps
}) {
// Debug render
useDebugRender('Dashboard', { user, theme, tasks });
return (
<div
className={`dashboard theme-${theme}`}
{...otherProps}
>
<header className="dashboard-header">
<UserInfo user={user} />
<ThemeToggle
theme={theme}
onToggle={onThemeToggle}
/>
</header>
<main className="dashboard-content">
<TaskList
tasks={tasks}
userRole={user.role}
/>
</main>
</div>
);
}
// Example usage
function App() {
const [theme, setTheme] = useState('light');
const [tasks, setTasks] = useState([
{ id: 1, title: 'Complete tutorial', done: false },
{ id: 2, title: 'Practice React', done: true }
]);
return (
<Dashboard
user={{
name: 'Alice',
role: 'Admin'
}}
theme={theme}
onThemeToggle={() =>
setTheme(t => t === 'light' ? 'dark' : 'light')
}
tasks={tasks}
data-testid="main-dashboard"
/>
);
}