React Components
Components are the building blocks of every React application. A component is a reusable, self-contained piece of UI that accepts inputs (props) and returns React elements describing what should appear on screen. Modern React exclusively uses function components — class components are legacy.
Good component design is the foundation of maintainable React code. Well-designed components are small, focused, reusable, and composable. They have clear interfaces (props), manage minimal state, and follow the single responsibility principle.
A functional component is a JavaScript function that accepts a props object and returns JSX. React calls the function to determine what to render. When props or state change, React calls the function again.
| 1 | // Simple function component |
| 2 | function Greeting({ name }) { |
| 3 | return <h1>Hello, {name}!</h1>; |
| 4 | } |
| 5 | |
| 6 | // Arrow function component (equivalent) |
| 7 | const Greeting = ({ name }) => <h1>Hello, {name}!</h1>; |
| 8 | |
| 9 | // Component with logic |
| 10 | function UserCard({ user, showEmail = false }) { |
| 11 | const initials = user.name |
| 12 | .split(" ") |
| 13 | .map(n => n[0]) |
| 14 | .join("") |
| 15 | .toUpperCase(); |
| 16 | |
| 17 | const memberSince = new Date(user.createdAt).getFullYear(); |
| 18 | |
| 19 | return ( |
| 20 | <div className="user-card"> |
| 21 | <div className="avatar">{initials}</div> |
| 22 | <h3>{user.name}</h3> |
| 23 | {showEmail && <p className="text-gray-500">{user.email}</p>} |
| 24 | <p className="text-sm text-gray-400">Member since {memberSince}</p> |
| 25 | </div> |
| 26 | ); |
| 27 | } |
| 28 | |
| 29 | // Usage |
| 30 | <UserCard user={{ name: "Jane Doe", email: "jane@example.com", createdAt: "2020-03-15" }} showEmail /> |
info
Props (short for "properties") are how data flows from parent to child. Props are read-only — a component must never modify its own props. Destructure props in the function signature for clarity.
| 1 | // Props interface with TypeScript |
| 2 | interface ButtonProps { |
| 3 | children: React.ReactNode; |
| 4 | variant?: "primary" | "secondary" | "danger"; |
| 5 | size?: "sm" | "md" | "lg"; |
| 6 | disabled?: boolean; |
| 7 | onClick?: () => void; |
| 8 | type?: "button" | "submit" | "reset"; |
| 9 | } |
| 10 | |
| 11 | function Button({ |
| 12 | children, |
| 13 | variant = "primary", |
| 14 | size = "md", |
| 15 | disabled = false, |
| 16 | onClick, |
| 17 | type = "button", |
| 18 | }: ButtonProps) { |
| 19 | const baseClasses = "font-mono rounded transition-colors"; |
| 20 | const sizeClasses = { |
| 21 | sm: "px-2 py-1 text-xs", |
| 22 | md: "px-4 py-2 text-sm", |
| 23 | lg: "px-6 py-3 text-base", |
| 24 | }; |
| 25 | const variantClasses = { |
| 26 | primary: "bg-blue-600 text-white hover:bg-blue-700", |
| 27 | secondary: "bg-gray-600 text-white hover:bg-gray-700", |
| 28 | danger: "bg-red-600 text-white hover:bg-red-700", |
| 29 | }; |
| 30 | |
| 31 | return ( |
| 32 | <button |
| 33 | type={type} |
| 34 | disabled={disabled} |
| 35 | onClick={onClick} |
| 36 | className={`${baseClasses} ${sizeClasses[size]} ${variantClasses[variant]} |
| 37 | ${disabled ? "opacity-50 cursor-not-allowed" : ""}`} |
| 38 | > |
| 39 | {children} |
| 40 | </button> |
| 41 | ); |
| 42 | } |
| 43 | |
| 44 | // Usage |
| 45 | <Button variant="primary" size="lg" onClick={handleSubmit}> |
| 46 | Submit Form |
| 47 | </Button> |
| 48 | <Button variant="danger" size="sm" disabled> |
| 49 | Delete |
| 50 | </Button> |
Children prop — the most important special prop:
| 1 | // children — any JSX passed between opening and closing tags |
| 2 | function Card({ title, children, footer }) { |
| 3 | return ( |
| 4 | <div className="card"> |
| 5 | <div className="card-header"> |
| 6 | <h3>{title}</h3> |
| 7 | </div> |
| 8 | <div className="card-body">{children}</div> |
| 9 | {footer && <div className="card-footer">{footer}</div>} |
| 10 | </div> |
| 11 | ); |
| 12 | } |
| 13 | |
| 14 | // Usage — children goes between the tags |
| 15 | <Card |
| 16 | title="User Profile" |
| 17 | footer={<button>Save Changes</button>} |
| 18 | > |
| 19 | <p>Name: Jane Doe</p> |
| 20 | <p>Email: jane@example.com</p> |
| 21 | <Input label="Bio" multiline /> |
| 22 | </Card> |
| 23 | |
| 24 | // children can be anything — elements, text, functions |
| 25 | <Card title="Dynamic Content"> |
| 26 | {(isOpen) => isOpen ? <ExpandedView /> : <CollapsedView />} |
| 27 | </Card> |
| 28 | |
| 29 | // Render prop pattern — children as a function |
| 30 | function DataFetcher({ url, children }) { |
| 31 | const { data, loading, error } = useFetch(url); |
| 32 | return children({ data, loading, error }); |
| 33 | } |
| 34 | |
| 35 | // Usage |
| 36 | <DataFetcher url="/api/users"> |
| 37 | {({ data, loading, error }) => { |
| 38 | if (loading) return <Spinner />; |
| 39 | if (error) return <ErrorMessage error={error} />; |
| 40 | return <UserList users={data} />; |
| 41 | }} |
| 42 | </DataFetcher> |
note
Composition is the core design principle in React. Instead of inheriting from base classes, you compose behavior by combining smaller components. This leads to more flexible, testable, and reusable code.
| 1 | // Composition: small components combined into larger ones |
| 2 | function PageLayout({ sidebar, children }) { |
| 3 | return ( |
| 4 | <div className="flex min-h-screen"> |
| 5 | <aside className="w-64 border-r">{sidebar}</aside> |
| 6 | <main className="flex-1 p-8">{children}</main> |
| 7 | </div> |
| 8 | ); |
| 9 | } |
| 10 | |
| 11 | function Dashboard({ user }) { |
| 12 | return ( |
| 13 | <PageLayout |
| 14 | sidebar={ |
| 15 | <nav> |
| 16 | <NavLink to="/dashboard">Overview</NavLink> |
| 17 | <NavLink to="/analytics">Analytics</NavLink> |
| 18 | <NavLink to="/settings">Settings</NavLink> |
| 19 | </nav> |
| 20 | } |
| 21 | > |
| 22 | <h1>Welcome back, {user.name}</h1> |
| 23 | <StatsGrid /> |
| 24 | <RecentActivity /> |
| 25 | </PageLayout> |
| 26 | ); |
| 27 | } |
| 28 | |
| 29 | // Slots pattern — named children |
| 30 | function Dialog({ title, children, actions }) { |
| 31 | return ( |
| 32 | <div className="dialog"> |
| 33 | <div className="dialog-header">{title}</div> |
| 34 | <div className="dialog-body">{children}</div> |
| 35 | <div className="dialog-actions">{actions}</div> |
| 36 | </div> |
| 37 | ); |
| 38 | } |
| 39 | |
| 40 | // Usage with named slots |
| 41 | <Dialog |
| 42 | title="Confirm Deletion" |
| 43 | actions={ |
| 44 | <> |
| 45 | <Button onClick={onCancel}>Cancel</Button> |
| 46 | <Button variant="danger" onClick={onConfirm}>Delete</Button> |
| 47 | </> |
| 48 | } |
| 49 | > |
| 50 | <p>Are you sure you want to delete this item? This cannot be undone.</p> |
| 51 | </Dialog> |
Fragments let you group multiple elements without adding an extra DOM node. They are essential when a component needs to return multiple elements without wrapping them in a div.
| 1 | import { Fragment } from "react"; |
| 2 | |
| 3 | // Without fragment — adds unnecessary div |
| 4 | function BadTableHeader() { |
| 5 | return ( |
| 6 | <div> {/* This div breaks table semantics! */} |
| 7 | <th>Name</th> |
| 8 | <th>Email</th> |
| 9 | <th>Role</th> |
| 10 | </div> |
| 11 | ); |
| 12 | } |
| 13 | |
| 14 | // With Fragment — no extra DOM node |
| 15 | function TableHeader() { |
| 16 | return ( |
| 17 | <> |
| 18 | <th>Name</th> |
| 19 | <th>Email</th> |
| 20 | <th>Role</th> |
| 21 | </> |
| 22 | ); |
| 23 | } |
| 24 | |
| 25 | // Long-form Fragment when you need a key |
| 26 | function DescriptionList({ items }) { |
| 27 | return ( |
| 28 | <dl> |
| 29 | {items.map(item => ( |
| 30 | <Fragment key={item.id}> |
| 31 | <dt>{item.label}</dt> |
| 32 | <dd>{item.value}</dd> |
| 33 | </Fragment> |
| 34 | ))} |
| 35 | </dl> |
| 36 | ); |
| 37 | } |
| 38 | |
| 39 | // Fragment for returning multiple elements from a component |
| 40 | function SiteMetadata() { |
| 41 | return ( |
| 42 | <> |
| 43 | <meta charSet="utf-8" /> |
| 44 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 45 | <title>My App</title> |
| 46 | <meta name="description" content="App description" /> |
| 47 | </> |
| 48 | ); |
| 49 | } |
info
React supports several patterns for conditionally rendering UI elements. Choose the pattern based on readability and complexity.
| 1 | function UserStatus({ user, isLoggedIn, notifications }) { |
| 2 | // Pattern 1: if/else with early returns |
| 3 | if (!user) { |
| 4 | return <LoginForm />; |
| 5 | } |
| 6 | |
| 7 | // Pattern 2: Ternary operator (inline) |
| 8 | return ( |
| 9 | <div> |
| 10 | <h1>{isLoggedIn ? `Welcome, ${user.name}` : "Welcome, Guest"}</h1> |
| 11 | |
| 12 | {/* Pattern 3: Logical AND (&&) */} |
| 13 | {notifications.length > 0 && ( |
| 14 | <div className="badge">{notifications.length} new</div> |
| 15 | )} |
| 16 | |
| 17 | {/* Pattern 4: Variable assignment */} |
| 18 | {(() => { |
| 19 | switch (user.role) { |
| 20 | case "admin": |
| 21 | return <AdminPanel />; |
| 22 | case "editor": |
| 23 | return <EditorPanel />; |
| 24 | default: |
| 25 | return <ViewerPanel />; |
| 26 | } |
| 27 | })()} |
| 28 | |
| 29 | {/* Pattern 5: Lookup table */} |
| 30 | {({ |
| 31 | admin: <AdminDashboard />, |
| 32 | editor: <EditorDashboard />, |
| 33 | viewer: <ViewerDashboard />, |
| 34 | })[user.role]} |
| 35 | |
| 36 | {/* Pattern 6: Nullish coalescing for fallback */} |
| 37 | {user.avatar ?? <DefaultAvatar />} |
| 38 | </div> |
| 39 | ); |
| 40 | } |
Rendering lists in React uses Array.map(). Each list item needs a stable, unique key prop so React can efficiently update the DOM when items are added, removed, or reordered.
| 1 | function TodoList({ todos, onToggle, onDelete }) { |
| 2 | return ( |
| 3 | <ul> |
| 4 | {todos.map(todo => ( |
| 5 | <li key={todo.id} className={todo.done ? "line-through" : ""}> |
| 6 | <input |
| 7 | type="checkbox" |
| 8 | checked={todo.done} |
| 9 | onChange={() => onToggle(todo.id)} |
| 10 | /> |
| 11 | <span>{todo.text}</span> |
| 12 | <button onClick={() => onDelete(todo.id)}>×</button> |
| 13 | </li> |
| 14 | ))} |
| 15 | </ul> |
| 16 | ); |
| 17 | } |
| 18 | |
| 19 | // Key rules: |
| 20 | // ✅ Use unique IDs (database IDs, UUIDs) |
| 21 | // key={user.id} |
| 22 | |
| 23 | // ✅ Use stable identifiers |
| 24 | // key={`item-${index}`} // only if list is static |
| 25 | |
| 26 | // ❌ Never use array index for dynamic lists |
| 27 | // key={index} // causes bugs when items reorder |
| 28 | |
| 29 | // Grouped lists with keys |
| 30 | function GroupedUsers({ groups }) { |
| 31 | return ( |
| 32 | <div> |
| 33 | {groups.map(group => ( |
| 34 | <div key={group.id}> |
| 35 | <h3>{group.name}</h3> |
| 36 | {group.users.map(user => ( |
| 37 | <div key={user.id}>{user.name}</div> |
| 38 | ))} |
| 39 | </div> |
| 40 | ))} |
| 41 | </div> |
| 42 | ); |
| 43 | } |
warning
The render prop pattern passes a function as a prop (or children) that the component calls to render its content. It gives parent components control over what is rendered while the child manages behavior.
| 1 | // Render prop component — manages mouse tracking logic |
| 2 | function MouseTracker({ render }) { |
| 3 | const [position, setPosition] = useState({ x: 0, y: 0 }); |
| 4 | |
| 5 | useEffect(() => { |
| 6 | const handleMouseMove = (e) => { |
| 7 | setPosition({ x: e.clientX, y: e.clientY }); |
| 8 | }; |
| 9 | window.addEventListener("mousemove", handleMouseMove); |
| 10 | return () => window.removeEventListener("mousemove", handleMouseMove); |
| 11 | }, []); |
| 12 | |
| 13 | return render(position); |
| 14 | } |
| 15 | |
| 16 | // Usage — parent decides how to display the mouse position |
| 17 | function App() { |
| 18 | return ( |
| 19 | <div> |
| 20 | <MouseTracker |
| 21 | render={({ x, y }) => ( |
| 22 | <p>Mouse is at ({x}, {y})</p> |
| 23 | )} |
| 24 | /> |
| 25 | <MouseTracker |
| 26 | render={({ x, y }) => ( |
| 27 | <Crosshair x={x} y={y} /> |
| 28 | )} |
| 29 | /> |
| 30 | </div> |
| 31 | ); |
| 32 | } |
| 33 | |
| 34 | // Toggle component with render prop |
| 35 | function Toggle({ initial = false, children }) { |
| 36 | const [on, setOn] = useState(initial); |
| 37 | const toggle = () => setOn(prev => !prev); |
| 38 | |
| 39 | return children({ on, toggle }); |
| 40 | } |
| 41 | |
| 42 | // Usage |
| 43 | <Toggle initial={true}> |
| 44 | {({ on, toggle }) => ( |
| 45 | <div> |
| 46 | <button onClick={toggle}>{on ? "ON" : "OFF"}</button> |
| 47 | {on && <p>Content is visible!</p>} |
| 48 | </div> |
| 49 | )} |
| 50 | </Toggle> |
Well-designed components follow these principles. Following them leads to code that is easier to test, reuse, and maintain.
| 1 | // 1. Single Responsibility — one reason to change |
| 2 | // ❌ Bad: component does too many things |
| 3 | function UserProfile({ userId }) { |
| 4 | const [user, setUser] = useState(null); |
| 5 | const [posts, setPosts] = useState([]); |
| 6 | const [editing, setEditing] = useState(false); |
| 7 | const [formData, setFormData] = useState({}); |
| 8 | // ... fetch, validate, save, delete, etc. |
| 9 | } |
| 10 | |
| 11 | // ✅ Good: split into focused components |
| 12 | function UserProfile({ userId }) { |
| 13 | return ( |
| 14 | <UserDataFetcher userId={userId}> |
| 15 | {({ user }) => ( |
| 16 | <UserDetails user={user} /> |
| 17 | )} |
| 18 | </UserDataFetcher> |
| 19 | ); |
| 20 | } |
| 21 | |
| 22 | // 2. Favor composition over configuration |
| 23 | // ❌ Bad: too many props to configure behavior |
| 24 | <Card |
| 25 | variant="outlined" |
| 26 | headerVisible={true} |
| 27 | footerVisible={true} |
| 28 | hoverable={true} |
| 29 | clickable={true} |
| 30 | avatarPosition="left" |
| 31 | badgeColor="green" |
| 32 | /> |
| 33 | |
| 34 | // ✅ Good: compose instead |
| 35 | <Card> |
| 36 | <CardHeader> |
| 37 | <Avatar user={user} /> |
| 38 | <Badge color="green">Active</Badge> |
| 39 | </CardHeader> |
| 40 | <CardBody>{content}</CardBody> |
| 41 | <CardFooter> |
| 42 | <Button>Action</Button> |
| 43 | </CardFooter> |
| 44 | </Card> |
| 45 | |
| 46 | // 3. Keep components pure — no side effects in render |
| 47 | // ❌ Bad: side effect during render |
| 48 | function BadComponent({ items }) { |
| 49 | document.title = `${items.length} items`; // side effect! |
| 50 | return <ul>{items.map(...)}</ul>; |
| 51 | } |
| 52 | |
| 53 | // ✅ Good: use useEffect for side effects |
| 54 | function GoodComponent({ items }) { |
| 55 | useEffect(() => { |
| 56 | document.title = `${items.length} items`; |
| 57 | }, [items.length]); |
| 58 | return <ul>{items.map(...)}</ul>; |
| 59 | } |
best practice
Error boundaries catch JavaScript errors anywhere in their child component tree during rendering, lifecycle methods, and constructors. They display a fallback UI instead of crashing the entire app.
| 1 | import { Component } from "react"; |
| 2 | |
| 3 | class ErrorBoundary extends Component { |
| 4 | constructor(props) { |
| 5 | super(props); |
| 6 | this.state = { hasError: false, error: null }; |
| 7 | } |
| 8 | |
| 9 | static getDerivedStateFromError(error) { |
| 10 | return { hasError: true, error }; |
| 11 | } |
| 12 | |
| 13 | componentDidCatch(error, errorInfo) { |
| 14 | console.error("Error caught by boundary:", error, errorInfo); |
| 15 | // Report to error tracking service |
| 16 | reportError(error, errorInfo); |
| 17 | } |
| 18 | |
| 19 | render() { |
| 20 | if (this.state.hasError) { |
| 21 | return ( |
| 22 | <div className="error-fallback"> |
| 23 | <h2>Something went wrong</h2> |
| 24 | <p>{this.state.error?.message}</p> |
| 25 | <button onClick={() => this.setState({ hasError: false })}> |
| 26 | Try Again |
| 27 | </button> |
| 28 | </div> |
| 29 | ); |
| 30 | } |
| 31 | return this.props.children; |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | // Usage — wrap components that might fail |
| 36 | function App() { |
| 37 | return ( |
| 38 | <div> |
| 39 | <Header /> |
| 40 | <ErrorBoundary> |
| 41 | <Dashboard /> {/* If this crashes, the rest of the app survives */} |
| 42 | </ErrorBoundary> |
| 43 | <Footer /> |
| 44 | </div> |
| 45 | ); |
| 46 | } |
note