Part 1.2 — Understanding Components and Props in React
If you imagine your React app as a system of LEGO bricks, components are the pieces.
Each brick is reusable, customizable, and fits perfectly into others — and props are the connectors that define how they interact.
Let’s break down how to write components that are clear, type-safe, and production-ready.
1. What Is a Component?
A React component is a reusable function that returns UI elements — typically JSX.
In modern React, all components should be functional (not class-based).
Here’s the simplest example:
function Welcome() {
return <h1>Welcome to Full-Stack Academy!</h1>;
}Usage:
<Welcome />This renders a <h1> on the page. Simple, but powerful.
2. Props: Making Components Dynamic
Props let components receive data from their parent.
function Welcome({ name }: { name: string }) {
return <h1>Welcome, {name}!</h1>;
}Usage:
<Welcome name="Ava" />Here, { name } is a prop (short for "property") — a parameter passed into the component.
3. Typing Props with Interfaces
When using TypeScript, always define a dedicated interface or type for props.
It makes the code readable and prevents bugs.
interface WelcomeProps {
name: string;
age?: number; // optional
}
export function Welcome({ name, age }: WelcomeProps) {
return (
<section>
<h1>Hello, {name}!</h1>
{age && <p>Age: {age}</p>}
</section>
);
}Benefits:
Autocomplete in VS Code
Compile-time type checking
Self-documenting code
4. Default Props and Children
React components can include children — nested JSX passed between tags.
interface CardProps {
title: string;
children: React.ReactNode;
}
export function Card({ title, children }: CardProps) {
return (
<div className="p-4 border rounded-lg">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}Usage:
<Card title="User Info">
<p>Name: Ava</p>
<p>Email: ava@example.com</p>
</Card>Children let you nest content flexibly, making components composable.
5. Composition Over Inheritance
In React, composition replaces traditional inheritance.
Instead of extending base components, you nest smaller ones to build bigger ones.
Example:
function Avatar({ src }: { src: string }) {
return <img className="w-12 h-12 rounded-full" src={src} alt="User avatar" />;
}
function UserProfile({ name, avatar }: { name: string; avatar: string }) {
return (
<div className="flex items-center space-x-2">
<Avatar src={avatar} />
<span>{name}</span>
</div>
);
}Small, focused components = higher reusability and testability.
6. Reusable Component Patterns
Let’s create a simple reusable Button component that supports multiple variants.
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
}
export function Button({ label, onClick, variant = "primary" }: ButtonProps) {
const base = "px-4 py-2 rounded text-white font-semibold";
const styles =
variant === "primary" ? "bg-blue-600 hover:bg-blue-700" : "bg-gray-500 hover:bg-gray-600";
return (
<button className={`${base} ${styles}`} onClick={onClick}>
{label}
</button>
);
}Usage:
<Button label="Save" onClick={() => console.log("Saved!")} />
<Button label="Cancel" variant="secondary" onClick={() => console.log("Canceled!")} />TypeScript enforces valid variant values — no accidental typos.
7. Component Folder Structure
Organize components logically. A typical structure:
src/
├── components/
│ ├── ui/
│ │ ├── Button.tsx
│ │ ├── Card.tsx
│ ├── layout/
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ ├── features/
│ │ ├── UserProfile.tsxKeep each component small, focused, and testable.
Later, you’ll group related components by feature (e.g., users, tasks, auth).
8. Prop Drilling (and When to Avoid It)
“Prop drilling” means passing props through multiple layers just to reach a deeply nested component.
Example:
<App>
<Dashboard user={user} />
</App>If <Dashboard> passes user through several layers just to reach <UserCard>, that’s prop drilling.
Later (in Part 6), we’ll solve this elegantly using Context and Reducers.
9. Debugging Component Props
Use browser dev tools or VS Code’s TypeScript hints to inspect props.
Adding console.log(props) temporarily helps understand data flow during development.
In React DevTools, you can view each component’s props tree in real time — invaluable when debugging complex UIs.
10. Wrapping Up
You now know how to:
Define typed, reusable components
Pass and validate props
Compose layouts without inheritance
Build flexible UI structures that scale
These fundamentals make up 70% of daily React work.
In the next post — Part 1.3: Managing State with useState and useEffect — we’ll add behavior to our components and connect them to dynamic data.
Related
Part 2.5 — Consuming REST APIs with Shared Types & Error Handling Strategies
Real-world frontends need safety: typed API responses, predictable errors, and consistent client logic. This post teaches you the patterns professionals use to integrate React with REST cleanly.
Part 2.4 — UX State: Loading, Error, and Empty States in React
A React app becomes truly usable when it handles all states gracefully: loading, error, empty, and success. In this post, you’ll learn the UX patterns professionals rely on.
Part 2.3 — Handling Forms, Validation & Submission to REST Endpoints
Forms connect users to data. In this post, you’ll learn how to validate input, manage UX states, and submit cleanly typed data to real REST endpoints.
Comments