| name | React Component Structure |
| description | Best practices for structuring React components - function declarations, file organization, and Single Responsibility Principle |
React Component Structure
Best practices for structuring React components for readability, testability, and maintainability.
Function Declarations vs Const
Always use function declarations for components, not const with arrow functions.
❌ Don't
const UserProfile = ({ name, email }: Props) => {
return (
<div>
<h1>{name}</h1>
<p>{email}</p>
</div>
)
}
const UserProfile: React.FC<Props> = ({ name, email }) => {
return (
<div>
<h1>{name}</h1>
<p>{email}</p>
</div>
)
}
✅ Do
function UserProfile({ name, email }: Props) {
return (
<div>
<h1>{name}</h1>
<p>{email}</p>
</div>
);
}
Why?
- Better stack traces: Function declarations show the component name clearly in error stacks
- Hoisting: Can reference components before they're defined in the file
- Avoid React.FC pitfalls:
- Implicitly includes
children (often unwanted)
- Return type is
ReactNode which includes undefined (allows accidental undefined returns)
- Incompatible with some generics patterns
- Adds unnecessary abstraction
- Clearer intent: Function declarations signal "this is a component"
- TypeScript inference: Props type is explicit and clear
Single Responsibility Principle
Components should do one thing well. Avoid mixing concerns or creating "god components".
📖 For comprehensive SOLID principles including SRP, see: code-standards/rules/solid-principles.md
In React context, SRP means:
- Each component has one clear purpose
- Easy to name descriptively
- Easy to test in isolation
- Changes for one reason only
❌ Don't: Multiple helpers before component
function formatDate(date: Date): string {
return new Intl.DateTimeFormat('nl-NL').format(date);
}
function calculateAge(birthDate: Date): number {
const today = new Date();
const age = today.getFullYear() - birthDate.getFullYear();
return age;
}
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function formatPhoneNumber(phone: string): string {
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
function capitalizeWords(str: string): string {
return str.replace(/\b\w/g, (l) => l.toUpperCase());
}
function UserProfile({ user }: Props) {
return (
<div>
<h1>{capitalizeWords(user.name)}</h1>
<p>Age: {calculateAge(user.birthDate)}</p>
<p>Email: {user.email}</p>
<p>Phone: {formatPhoneNumber(user.phone)}</p>
</div>
);
}
✅ Do: Extract helpers to utility files
import { calculateAge, formatDate } from '@/utils/date-helpers';
import { formatPhoneNumber } from '@/utils/formatters';
import { capitalizeWords } from '@/utils/string-helpers';
import { validateEmail } from '@/utils/validators';
function UserProfile({ user }: Props) {
return (
<div>
<h1>{capitalizeWords(user.name)}</h1>
<p>Age: {calculateAge(user.birthDate)}</p>
<p>Email: {user.email}</p>
<p>Phone: {formatPhoneNumber(user.phone)}</p>
</div>
);
}
✅ Alternative: Place simple helpers after component
For component-specific helpers that are short (1-3 lines):
function UserCard({ user, onEdit }: Props) {
const displayName = formatDisplayName(user.firstName, user.lastName);
const initials = getInitials(user.firstName, user.lastName);
return (
<div>
<Avatar>{initials}</Avatar>
<h2>{displayName}</h2>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}
function formatDisplayName(first: string, last: string): string {
return `${first} ${last}`;
}
function getInitials(first: string, last: string): string {
return `${first[0]}${last[0]}`.toUpperCase();
}
Component Organization Pattern
Recommended file structure:
import { formatCurrency } from '@/utils/formatters';
import { useEffect, useState } from 'react';
const DISCOUNT_THRESHOLD = 100;
interface ProductCardProps {
product: Product;
onAddToCart: (id: string) => void;
}
function ProductCard({ product, onAddToCart }: ProductCardProps) {
const [quantity, setQuantity] = useState(1);
const discountedPrice = calculateDiscount(product.price, quantity);
return (
<div>
<h3>{product.name}</h3>
<p>{formatCurrency(discountedPrice)}</p>
<button onClick={() => onAddToCart(product.id)}>Add to Cart</button>
</div>
);
}
function calculateDiscount(price: number, qty: number): number {
return qty >= DISCOUNT_THRESHOLD ? price * 0.9 : price;
}
export { ProductCard };
When to Extract
Extract to separate file when:
- ✅ Helper is reused across 2+ components
- ✅ Logic is complex (>10 lines)
- ✅ Helper is domain logic (business rules)
- ✅ Helper needs separate testing
Keep in component file when:
- ✅ Used only in this component
- ✅ Simple (1-5 lines)
- ✅ Tightly coupled to component logic
- ✅ Pure formatting/transformation
Anti-Patterns
❌ Don't: God component
function Dashboard() {
return <>{/* 200 lines of JSX */}</>;
}
✅ Do: Compose smaller components
function Dashboard() {
return (
<DashboardLayout>
<UserHeader />
<FilterControls />
<DataTable />
<ChartSection />
</DashboardLayout>
);
}
❌ Don't: Mixing presentation and logic
function ProductList() {
const [products, setProducts] = useState([]);
const [filters, setFilters] = useState({});
return <>{/* Presentation */}</>;
}
✅ Do: Separate concerns
function ProductList() {
const { products, isLoading } = useProducts();
if (isLoading) {
return <LoadingSpinner />;
}
return <ProductGrid products={products} />;
}
Summary
| Aspect | Guideline |
|---|
| Component syntax | Function declarations, not const |
| React.FC | Avoid (unnecessary, has pitfalls) |
| Helpers | Extract to utils if reused or complex |
| Component size | Keep focused (~50-100 lines) |
| Organization | Imports → Constants → Props → Component → Helpers → Export |
| Props location | Define immediately before component |
| Single Responsibility | One component, one purpose |
Principle: Components should be easy to understand at a glance. If you have to scroll past 5 helpers to find the component, refactor.