Master the foundation of modern React development. Learn how component-based architecture, JSX, unidirectional props flow, and stateful memory (useState) enable predictable, high-performance user interfaces for production Full Stack applications.
From Imperative DOM Spaghetti to Declarative Component Architecture
In traditional vanilla JavaScript, building an interactive application requires imperative DOM manipulation. When an item is clicked or an API response arrives, you have to manually find the DOM element with document.getElementById(), attach event listeners, update innerHTML, toggle CSS classes, and keep the browser view in sync with your JavaScript data. As applications grow, this creates tangled spaghetti code where data and DOM state easily diverge.
React is a JavaScript library for building user interfaces. Instead of manually mutating DOM elements step-by-step, you write declarative code: you describe what the UI should look like for any given state, and React takes care of efficiently updating the actual browser DOM whenever your state changes:
React applications are structured as a tree of components. A component is an independent, reusable piece of UI. Consider how a real-world learning platform dashboard is decomposed:
The Golden Rule of React Components:A React component is just a JavaScript function that returns UI.
Syntax Extension That Merges Markup with the Power of JavaScript
JSX looks like HTML, but under the hood, it is a syntax extension for JavaScript. Every JSX tag is transformed by modern compilers into JavaScript function calls (like jsx('div', { ... })). Because JSX is JavaScript, certain rules apply:
{}: Any valid JavaScript expression (variables, calculations, ternary checks, function calls) can be embedded directly inside curly braces.className instead of class: Because class is a reserved keyword in JavaScript, HTML classes in JSX are written as className="course-card".<img />, <input />, and <br />) must be explicitly self-closed with />.<>...</>): A component can only return a single root JSX element. If you have multiple siblings without wanting to introduce an unnecessary wrapper <div> in the DOM, wrap them in a Fragment.// Notice: Function name is PascalCase (CourseCard), not lowercase!
function CourseCard({ title, level, isEnrolled }) {
// JavaScript logic before the return
const formattedLevel = level.toUpperCase();
return (
<article className="course-card">
{/* JavaScript expressions evaluated dynamically */}
<h2>{title}</h2>
<p className="level-badge">{formattedLevel}</p>
{/* Self-closing tag with dynamic attribute */}
<input type="checkbox" checked={isEnrolled} readOnly />
</article>
);
}Unidirectional Data Flow: Passing Information from Parent to Child
React components use props (short for properties) to communicate with each other. Every parent component can pass information to its child components by giving them attributes, just like HTML attributes.
A component must never modify its own props. If a child needs to react to changes, the parent must pass new prop values or pass a callback.
Instead of writing props.title and props.level, destructure arguments directly: { title, level, children }.
// 1. Parent Component passes data down
function App() {
return (
<main className="catalog">
<CourseCard
title="React 19 Fundamentals"
level="Beginner"
/>
<CourseCard
title="PostgreSQL Advanced Schema Design"
level="Intermediate"
/>
</main>
);
}
// 2. Child Component receives props and renders UI
function CourseCard({ title, level }) {
return (
<div className="card">
<h3>{title}</h3>
<span className="badge">{level}</span>
</div>
);
}Giving Memory to Components and Responding to User Interactions
When a function executes, its local variables are created and then discarded when the function returns. Furthermore, modifying a plain JavaScript variable does not notify React that it needs to update the DOM. To retain data between render cycles and trigger an automatic UI update, React provides the useState Hook:
import { useState } from 'react';
function EnrollmentButton() {
// 1. Declare state variable and its setter function
const [isEnrolled, setIsEnrolled] = useState(false);
// 2. Event handler function
function handleClick() {
// Calling the setter triggers React to re-render the component with the new value!
setIsEnrolled(prev => !prev);
}
return (
<button
type="button"
className={isEnrolled ? "btn-enrolled" : "btn-enroll"}
onClick={handleClick}
>
{isEnrolled ? "Enrolled ✓" : "Enroll"}
</button>
);
}Rendering Dynamic Collections and Context-Aware UI
.map() and the Importance of keyIn React, you transform arrays of data into arrays of JSX elements using standard JavaScript Array.prototype.map(). Every item rendered in a list must have a unique, stable key prop:
function CourseList({ courses }) {
// Pattern 1: Empty state conditional check
if (courses.length === 0) {
return <p className="empty">No courses found matching your criteria.</p>;
}
// Pattern 2: List rendering with unique, stable ID keys
return (
<div className="course-grid">
{courses.map(course => (
<CourseCard
key={course.id} // ⚠️ CRITICAL: Stable key helps React track DOM elements
course={course}
/>
))}
</div>
);
}key={course.id} uniquely identifies the record even if the list is sorted, filtered, or items are removed.
key={index} causes bugs when items are reordered or deleted because indices change dynamically between renders.
Main Practical Activity: Executable Component Architecture & State Management
CourseCardpropsenrolledIds state in parentonToggleEnroll callback down to cards.map() and stable key={course.id}Data Flows Down via Props, Events Flow Up via Callbacks
In React, components don’t talk directly across siblings. Instead, communication strictly follows a single, predictable cycle:
Lifting State Up: If two child components need to share data (e.g., a search bar component and a course list component), move the state up to their closest common parent, and pass it down as props.
Interactive Anti-Pattern Sandbox: 5 Real Pitfalls to Avoid
// ❌ BROKEN: React thinks <courseCard /> is a built-in HTML element!
function courseCard({ title }) {
return <div>{title}</div>;
}
// In parent render:
return <courseCard title="React 19" />;// ❌ BROKEN: Invokes handleEnroll during render phase!
function CourseCard({ id, onEnroll }) {
// If onEnroll sets state, this causes: "Too many re-renders. React limits the number of renders to prevent an infinite loop."
return <button onClick={onEnroll(id)}>Enroll</button>;
}() execute the function immediately during component rendering rather than waiting for the click event.// ❌ BROKEN: Direct array mutation
const [courses, setCourses] = useState(['React 19']);
function addCourse(title) {
courses.push(title); // Mutates original array in place!
setCourses(courses); // Memory reference is identical: React skips render!
}courses === courses (same pointer).// ❌ BROKEN: Using index as key in a mutable, sortable list
{courses.map((course, index) => (
<CourseCard key={index} course={course} />
))}// ❌ BROKEN: Modifying incoming props object
function CourseCard(props) {
props.title = props.title.toUpperCase(); // Throws TypeError in Strict Mode!
return <h3>{props.title}</h3>;
}Apply Components, Props, Dynamic Keys, and Completion Toggle State
Below is an interactive Learning Resource tracker. Toggle completion states, filter by resource type, and observe how state and keys keep each item isolated:
Here is the definitive architectural flow for building interactive user interfaces with React:
{}, className, and Fragments.useState. Updating state via its setter schedules React to re-render the UI..map() for correct DOM reconciliation.Validate your understanding of components, JSX expressions, props, stateful re-rendering, event handlers, and list keys.