AiTechWorlds
AiTechWorlds
A React component is a self-contained, reusable description of a piece of UI for a given set of inputs — not a set of instructions for changing the page.
It works like a LEGO brick: each brick has a fixed shape and snaps into a larger structure regardless of what's built around it. You don't reshape a brick to fit a specific model — you design bricks that combine well, then compose them into anything from a small toy to a castle. React apps are built the same way: small components combine into pages.
A component is a function that takes some input (props) and returns what to display (JSX). That's it.
function WelcomeBanner({ name, isPremium }: { name: string; isPremium: boolean }) {
return (
<div className={`banner ${isPremium ? "banner-gold" : "banner-free"}`}>
<h2>Welcome back, {name}!</h2>
{isPremium && <span className="badge">Pro Member</span>}
</div>
);
}The function describes the output. Every time name or isPremium changes, React calls the function again and updates the DOM to match.
React applications are trees of components. At the root is your App or RootLayout. Everything else is children, grandchildren, and deeper.
App
├── Header
│ ├── Logo
│ ├── Nav
│ │ ├── NavLink (×4)
│ └── UserMenu
│ ├── Avatar
│ └── Dropdown
├── Main
│ └── CoursesPage
│ ├── SearchBar
│ ├── FilterPanel
│ └── CourseGrid
│ └── CourseCard (×12)
└── FooterEach box is a component. Each component manages its own piece of UI. Changes are localized — when SearchBar state changes, only SearchBar and its children re-render.
When you see a design, ask three questions:
1. What data changes? Each changing data point is state.
2. Where does the data live? State lives in the lowest common ancestor of all components that need it.
3. What are the natural visual blocks? Each reusable section is a candidate for a component.
// Looking at a product page:
// The whole page — one component
<ProductPage>
// The gallery changes which image is selected — needs state
<ImageGallery images={product.images} />
// Static info — just props
<ProductInfo
title={product.title}
price={product.price}
description={product.description}
/>
// Quantity + add to cart changes cart state
<AddToCartForm productId={product.id} />
// Reviews has its own loading state
<Reviews productId={product.id} />
</ProductPage>Each component should have one job. Signs a component needs to be split:
// Wrong: doing too much
function UserDashboard() {
// Fetching users
// Filtering users
// Displaying users
// Handling pagination
// Displaying a modal
// Handling form submission
// ... 300 lines
}
// Correct: each component has one responsibility
function UserDashboard() {
const [selectedUser, setSelectedUser] = useState<User | null>(null);
return (
<div>
<UserTable onSelectUser={setSelectedUser} />
{selectedUser && (
<UserEditModal user={selectedUser} onClose={() => setSelectedUser(null)} />
)}
</div>
);
}
function UserTable({ onSelectUser }: { onSelectUser: (user: User) => void }) {
// All the user listing logic here
}
function UserEditModal({ user, onClose }: { user: User; onClose: () => void }) {
// All the editing logic here
}Props: data that comes FROM a parent. Read-only inside the component.
State: data that BELONGS to the component. Can be changed by calling a setter function.
// Wrong — trying to mutate props
function CounterWrong({ count }: { count: number }) {
return <button onClick={() => count++}>Clicked {count}</button>;
// ^^^^ This is wrong — can't mutate props
}
// Correct patterns
// Option 1: If this component owns the count, use state
function CounterOwned() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>Clicked {count}</button>;
}
// Option 2: If the parent owns the count, accept a callback
function CounterControlled({ count, onIncrement }: { count: number; onIncrement: () => void }) {
return <button onClick={onIncrement}>Clicked {count}</button>;
}A controlled component is fully driven by React state. An uncontrolled component manages its own internal state (like a native <input> with no value prop):
// Uncontrolled — the DOM owns the value
function UncontrolledInput() {
const inputRef = useRef<HTMLInputElement>(null);
function handleSubmit() {
console.log(inputRef.current?.value);
}
return <input ref={inputRef} />;
}
// Controlled — React owns the value
function ControlledInput() {
const [value, setValue] = useState("");
return <input value={value} onChange={e => setValue(e.target.value)} />;
}Controlled is generally preferred — you can validate, transform, or respond to every keystroke. Use uncontrolled when you just need the final value (a simple search box that submits on Enter).
For complex components with multiple related parts, the compound components pattern keeps the API clean:
// Instead of prop-heavy APIs:
<Tabs
tabs={[...]}
activeTab={0}
onTabChange={setTab}
tabContent={[...]}
/>
// Compound components read naturally:
<Tabs defaultTab="overview">
<Tabs.List>
<Tabs.Tab value="overview">Overview</Tabs.Tab>
<Tabs.Tab value="curriculum">Curriculum</Tabs.Tab>
<Tabs.Tab value="reviews">Reviews</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="overview">Overview content...</Tabs.Panel>
<Tabs.Panel value="curriculum">Curriculum content...</Tabs.Panel>
<Tabs.Panel value="reviews">Reviews content...</Tabs.Panel>
</Tabs>Each sub-component gets its context from the parent via React Context, not props. The API looks like HTML — easy to read and extend.
Extract when:
Don't extract just for the sake of it. Three lines of JSX don't need their own component. A 20-line card that appears 15 times definitely does.
Next lesson: Props and component composition — passing data and building flexible component APIs.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises