Let's break down how to structure React user interfaces from scratch, focusing on component purity, proper JSX syntax, and scalable file organization.
What you'll learn:
- How to write your first modular React component with JSX.
- The rules of passing props and keeping components strictly pure.
- How to map collections cleanly with stable list keys.
- Understanding component trees and module boundaries.
Writing Your First Component and JSX Rules
React apps are built from small, isolated pieces of UI called components. A component is simply a JavaScript function that returns markup. However, JSX isn't raw HTML—it's stricter and compiles down to JavaScript function calls.
If you paste standard HTML directly into a component without checking syntax, your build will fail. Attributes like class must become className, and tags must be properly closed.
export default function UserProfileCard() {
return (
<div className="card">
<img
src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=400&q=80"
alt="Jane Doe profile avatar"
className="avatar-img"
/>
<h2>Jane Doe</h2>
<p>Senior Frontend Engineer</p>
</div>
);
}
Accessibility Note: Always include descriptive
alttext on images unless they are purely decorative. Screen readers rely on this context.
Opening a Window to JavaScript with Curly Braces
JSX keeps rendering logic and markup in the same place. When you want to render dynamic data, variables, or inline styles, use curly braces {} to inject JavaScript expressions directly into your markup.
This lets you compute values or pull properties directly from data objects without cluttering your return statement.
const engineer = {
name: 'Alex Rivera',
status: 'Online',
badgeStyle: {
backgroundColor: '#10B981',
color: '#ffffff',
padding: '2px 8px',
borderRadius: '9999px',
},
};
export default function StatusBadge() {
return (
<div className="flex items-center gap-2">
<span style={engineer.badgeStyle}>{engineer.status}</span>
<span className="font-medium">{engineer.name}</span>
</div>
);
}
Passing Data Down via Props
Parent components communicate with children by passing props. Props work just like HTML attributes, but they can accept any JavaScript value—strings, numbers, arrays, callback functions, or even child elements.
Destructure your props right in the function signature to keep your component inputs explicit and easy to read at a glance.
interface AvatarProps {
name: string;
imageUrl: string;
size?: number;
}
function Avatar({ name, imageUrl, size = 80 }: AvatarProps) {
return (
<img
src={imageUrl}
alt={name}
width={size}
height={size}
className="rounded-full object-cover"
/>
);
}
export default function UserProfile() {
return (
<div className="p-4 border rounded-lg shadow-sm">
<Avatar
name="Sarah Jenkins"
imageUrl="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80"
size={96}
/>
</div>
);
}
Keeping Components Strictly Pure
A pure component should always return the exact same JSX given the same props. Most importantly, it must mind its own business: never mutate variables or objects that existed before the function was called.
Mutating external state during render leads to unpredictable bugs that only show up under strict mode or concurrent rendering.
// ❌ BAD: Mutates an external variable during render
let ticketCounter = 0;
function ImpureTicket() {
ticketCounter++;
return <div>Ticket #{ticketCounter}</div>;
}
// ✅ GOOD: Pure function relying solely on props
interface PureTicketProps {
ticketNumber: number;
}
function PureTicket({ ticketNumber }: PureTicketProps) {
return <div>Ticket #{ticketNumber}</div>;
}
export default function TicketPrinter() {
return (
<div className="space-y-2">
<PureTicket ticketNumber={1} />
<PureTicket ticketNumber={2} />
<PureTicket ticketNumber={3} />
</div>
);
}
Speed Note: Keeping your render functions pure allows React to optimize rendering pipelines safely without side-effect race conditions.
Rendering Collections and Managing Keys
When rendering lists from an array, use JavaScript's map() method. Every item returned inside a list must have a unique key prop so React can track item identity when elements are reordered, inserted, or deleted.
Avoid using array indexes as keys if your list can reorder or mutate; use stable database IDs instead.
interface Task {
id: string;
title: string;
done: boolean;
}
const tasks: Task[] = [
{ id: 't-1', title: 'Refactor component trees', done: true },
{ id: 't-2', title: 'Optimize bundle size', done: false },
{ id: 't-3', title: 'Write unit tests', done: false },
];
export default function TaskList() {
return (
<ul className="divide-y divide-gray-200">
{tasks.map(task => (
<li key={task.id} className="py-2 flex items-center justify-between">
<span className={task.done ? 'line-through text-gray-400' : ''}>
{task.title}
</span>
<span>{task.done ? '✅' : '⏳'}</span>
</li>
))}
</ul>
);
}
Summary
Building robust React applications starts with mastering the fundamentals of component composition. By keeping your functions pure, leveraging JSX expressions correctly, passing typed props, and managing keys on lists, you lay a solid foundation for scalable UI architecture.
Next Steps:
- Extract your components into separate files and import them cleanly using ES modules.
- Explore React render trees to analyze how data flows down your component hierarchy.
- Dive into adding interactivity and managing local state with hooks like
useState.