Building Components
Learn to create reusable React components with TypeScript that can be used throughout the K12worX website.
What You'll Learn
- Component anatomy and structure
- TypeScript interfaces for props
- Client vs Server components
- Exporting and importing components
- Component best practices
Component Basics
What Makes a Component
A component is a function that returns JSX:
export default function MyComponent() {
return <div>Hello!</div>;
}
That's it! Everything else is building on this.
Exercise 1: Simple Component
Create a Button Component
File: components/ui/Button.tsx
interface ButtonProps {
text: string;
onClick?: () => void;
}
export default function Button({ text, onClick }: ButtonProps) {
return (
<button
onClick={onClick}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
{text}
</button>
);
}
Use It
import Button from '@/components/ui/Button';
<Button text="Click Me" onClick={() => alert('Clicked!')} />
TypeScript Props
Defining Props Interface
interface CardProps {
title: string; // Required
description?: string; // Optional (?)
image?: string;
onClick?: () => void;
}
Using Props
export default function Card({
title,
description,
image,
onClick
}: CardProps) {
return (
<div className="card" onClick={onClick}>
{image && <img src={image} alt={title} />}
<h3>{title}</h3>
{description && <p>{description}</p>}
</div>
);
}
Client vs Server Components
Server Components (Default)
Don't need "use client":
// This is a Server Component (default)
export default function TeamGrid() {
return (
<div className="grid grid-cols-3 gap-4">
{/* Static content */}
</div>
);
}
Benefits:
- Faster initial load
- Better SEO
- Can fetch data directly
Client Components
Need "use client" when using:
- State (
useState) - Effects (
useEffect) - Event handlers
- Browser APIs
"use client";
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicks: {count}
</button>
);
}
Exercise 2: Card Component
Create Reusable Card
File: components/ui/Card.tsx
interface CardProps {
title: string;
description: string;
icon?: string;
color?: string;
}
export default function Card({
title,
description,
icon,
color = "blue"
}: CardProps) {
return (
<div className={`p-6 bg-${color}-50 rounded-lg border border-${color}-200`}>
{icon && <div className="text-4xl mb-4">{icon}</div>}
<h3 className="text-xl font-semibold mb-2">{title}</h3>
<p className="text-gray-600">{description}</p>
</div>
);
}
Use It
import Card from '@/components/ui/Card';
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card
title="Feature 1"
description="Description here"
icon="šÆ"
color="blue"
/>
<Card
title="Feature 2"
description="Another description"
icon="š”"
color="purple"
/>
</div>
Component Patterns
Pattern 1: Layout Component
interface LayoutProps {
children: React.ReactNode;
title?: string;
}
export default function PageLayout({ children, title }: LayoutProps) {
return (
<div className="min-h-screen bg-white">
<div className="max-w-7xl mx-auto px-4 py-12">
{title && <h1 className="text-4xl font-bold mb-8">{title}</h1>}
{children}
</div>
</div>
);
}
Pattern 2: List Component
interface ListItem {
id: string;
name: string;
}
interface ListProps {
items: ListItem[];
onItemClick?: (id: string) => void;
}
export default function List({ items, onItemClick }: ListProps) {
return (
<ul className="space-y-2">
{items.map((item) => (
<li
key={item.id}
onClick={() => onItemClick?.(item.id)}
className="p-4 bg-gray-50 rounded hover:bg-gray-100 cursor-pointer"
>
{item.name}
</li>
))}
</ul>
);
}
Pattern 3: Conditional Rendering
interface AlertProps {
type: "success" | "error" | "warning";
message: string;
}
export default function Alert({ type, message }: AlertProps) {
const styles = {
success: "bg-green-100 text-green-900 border-green-200",
error: "bg-red-100 text-red-900 border-red-200",
warning: "bg-yellow-100 text-yellow-900 border-yellow-200",
};
return (
<div className={`p-4 rounded border ${styles[type]}`}>
{message}
</div>
);
}
Best Practices
1. One Component Per File
ā
components/ui/Button.tsx (only Button)
ā components/ui/AllComponents.tsx (Button + Card + Alert)
2. Clear Naming
// ā
Good
<UserProfile />
<NavigationMenu />
<ContactForm />
// ā Bad
<Component1 />
<Thing />
<Stuff />
3. Props Interface
// ā
Good
interface ButtonProps {
text: string;
onClick: () => void;
}
// ā Bad
function Button(props: any) {
4. Default Props
export default function Card({
color = "blue", // Default value
size = "medium",
}: CardProps) {
Quick Reference
Basic component:
export default function Component() {
return <div>Content</div>;
}
With props:
interface Props {
title: string;
}
export default function Component({ title }: Props) {
return <h1>{title}</h1>;
}
Client component:
"use client";
import { useState } from 'react';
export default function Component() {
const [state, setState] = useState(0);
return <div>{state}</div>;
}