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>;
}

What's Next

Working with Forms →