React
A quick reference for React — the JavaScript library used to build scalable front-ends out of reusable components. Covers the ES6+ and functional JavaScript foundations React is built on, the Virtual DOM, JSX, and writing first components.
Why React?
React.js is a JavaScript library developed by engineers at Facebook. No prior React knowledge is needed to learn it — start at the beginning and move slowly.
| Reason | Why it matters |
|---|---|
| Fast | Handles complex updates while still feeling quick and responsive. |
| Modular | Many smaller, reusable files instead of large, dense ones — a solution to JavaScript's maintainability problems. |
| Scalable | Performs best in large programs displaying a lot of changing data. |
| Flexible | Usable beyond web apps — its full potential is still being explored. |
| Popular | Not a quality signal by itself, but understanding React improves employability. |
JavaScript Refresher
Core JavaScript concepts to brush up on before starting React — syntax and functional programming ideas that come up constantly in React apps.
| Resource | Why it matters for React |
|---|---|
| MDN — Re-introduction to JavaScript | Guide to brush up on foundational JS syntax. |
| Introduction to Functional JavaScript | Covers the history of JS and its programming paradigms — functional programming underpins how React components are written. |
Destructuring (see Destructuring in the JavaScript cheatsheet) is widely used in React apps — worth being comfortable with before moving on.
The Virtual DOM
The Problem — DOM manipulation is slow, and most JS frameworks update it more than they have to. Example: a 10-item list where you check off one item — most frameworks rebuild the entire list, ten times more work than necessary, when only one item actually changed.
| Concept | Description |
|---|---|
| Virtual DOM object | A lightweight copy of a real DOM object, with the same properties — but no power to directly change what's on screen. React keeps one virtual DOM object for every real DOM object. |
| Render | Rendering a JSX element updates every single virtual DOM object. Sounds wasteful, but is insignificant in cost because the virtual DOM updates so quickly. |
| Diffing | React compares the freshly updated virtual DOM against a snapshot taken right before the update, to work out exactly which virtual DOM objects changed. |
| Real DOM update | Only the objects the diff found changed get updated on the real DOM — everything else is left alone. This is where React's reputation for performance comes from. |
Analogy:
Manipulating the real DOM → moving rooms in an actual house — slow, physical
Manipulating the virtual DOM → editing a blueprint — fast, nothing is built yet
Summary — updating the DOM in React
- The entire virtual DOM gets updated.
- The virtual DOM is compared (diffed) against a snapshot of what it looked like before the update — React figures out which objects changed.
- The changed objects, and only those objects, get updated on the real DOM.
- Changes on the real DOM cause the screen to change.
Intro to JSX
JSX is a syntax extension for JavaScript, written to be used with React. It looks like HTML but is not valid JavaScript on its own — a JSX compiler translates it into regular JavaScript before it reaches the browser.
| Concept | Description | Example |
|---|---|---|
| JSX element | The basic unit of JSX — looks exactly like HTML, but lives in a JS file. | <h1>Hello world</h1> |
| JSX as an expression | JSX elements are JavaScript expressions — they can be saved in variables, passed to functions, or stored in objects/arrays. | const navBar = <nav>...</nav>; |
| Attributes | Written like HTML attributes — name, equals sign, quoted value. | <a href='...'>Link</a> |
| Nesting | JSX elements can nest inside each other, just like HTML. Multi-line JSX must be wrapped in parentheses. | (<a><h1>...</h1></a>) |
| One outer element | A JSX expression must have exactly one outermost element — the first opening tag and last closing tag must belong to the same element. Fix multiple outer elements by wrapping them in a <div>. | <div><p>...</p><p>...</p></div> |
Rendering JSX
import React from 'react';
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<h1>Hello world</h1>);
| Line | What it does |
|---|---|
| document.getElementById('app') | Gets the real DOM element the app will render into. |
| createRoot(container) | Creates a React root at that element — the "where to render" part. |
| root.render(...) | Renders the JSX passed in — the "what to render" part. |
The argument to render() doesn't have to be JSX written inline — it can be a variable that evaluates to a JSX expression:
const toDoList = (
<ol>
<li>Learn React</li>
<li>Become a Developer</li>
</ol>
);
root.render(toDoList);
render() and the virtual DOM:
A React root's render() method only updates DOM elements that have
changed. Rendering the exact same JSX twice in a row does nothing the
second time — this is powered by the virtual DOM (see above).
Common mistakes
- Closing tag written as an opening tag — <li>text<li> instead of <li>text</li> (missing the /)
- Multiple outer elements in one JSX expression — wrap them in a single <div>
- Forgetting parentheses around a multi-line JSX expression
Note: both "double" and 'single' quotes are valid for JSX attribute values in JavaScript — but this course's autograder only accepted single quotes.
Review
- React is a modular, scalable, flexible, and popular front-end library
- JSX is a syntax extension letting you write HTML-like expressions in JS
- JSX elements can be stored in variables, objects, arrays, and more
- JSX elements can have attributes and be nested, just like HTML
- JSX must have exactly one outer element
- createRoot() (from react-dom/client) creates a React root at a DOM element
- A root's render() method renders JSX on the screen, and only updates what changed
Advanced JSX
Building on the basics — self-closing tags, mixing real JavaScript into JSX, conditionals, rendering lists, and what JSX actually compiles down to.
Self-Closing Tags
Single-tag elements (<img>, <br>, <input>) are optional to self-close with a slash in HTML, but the slash is required in JSX.
// Fine in HTML, NOT FINE in JSX:
<br>
// Required in JSX:
<br />
JavaScript In Your JSX
Anything between JSX tags is read as JSX (i.e. text), not JavaScript — so {2 + 3} is needed to actually evaluate an expression rather than print "2 + 3" as a string. Curly braces mark the boundary of a JavaScript injection into JSX, similar to how quotes mark string boundaries.
<h1>2 + 3</h1> // renders the text "2 + 3"
<h1>{2 + 3}</h1> // renders "5"
Variables declared outside a JSX expression are accessible inside it the same way, since it's all the same JavaScript environment:
const name = 'Gerdo';
const greeting = <p>Hello, {name}!</p>;
Variable Attributes
Attribute values are often set from variables or object properties — these need curly braces, not quotes, since they're JavaScript, not string literals.
const sideLength = '200px';
const panda = (
<img
src="images/panda.jpg"
height={sideLength}
width={sideLength} />
);
Easy mix-up: src="images/panda.jpg" (string literal → quotes) vs height={sideLength} (variable → curly braces).
Event Listeners
Written like HTML event attributes, but camelCase, and the value must be a function reference — not a function call.
function clickAlert() {
alert('You clicked this image!');
}
<img onClick={clickAlert} />
{clickAlert} passes the function itself — React calls it when the event fires. {clickAlert()} would call it immediately during render instead.
Conditionals in JSX
You can't inject a plain if statement into a JSX expression — { if (x) { ... } } breaks. Three ways around it:
| Approach | When to use | Example |
|---|---|---|
| if/else outside JSX | Build the whole element (or pick between two) before rendering. | if (x) { img = <A /> } else { img = <B /> } |
| Ternary ( ? : ) | Compact inline choice between two JSX outcomes. | { age >= 21 ? 'Buy Drink' : 'Do Teen Stuff' } |
| && | Render something or nothing at all — no "else" case needed. | { !judgmental && <li>Nacho Cheez</li> } |
// if/else — decide which element to build, outside the JSX
let img;
if (coinToss() === 'heads') {
img = <img src={pics.kitty} />;
} else {
img = <img src={pics.doggy} />;
}
// Ternary — inline, always renders one side or the other
const headline = <h1>{age >= drinkingAge ? 'Buy Drink' : 'Do Teen Stuff'}</h1>;
// && — renders the right side only if the left side is truthy, otherwise nothing
const tasty = (
<ul>
<li>Applesauce</li>
{!judgmental && <li>Nacho Cheez Straight Out The Jar</li>}
</ul>
);
.map() in JSX and Keys
.map() is the standard way to turn an array of data into an array of JSX elements. Lists built this way usually need a key attribute — a unique value per item that React uses internally to track list identity across renders.
const people = ['Rowe', 'Prevost', 'Gare'];
const peopleList = people.map((person, i) =>
<li key={'person_' + i}>{person}</li>
);
root.render(<ol>{peopleList}</ol>);
Keys are needed when either is true:
- List items have memory across renders (e.g. a checked-off to-do item)
- The list's order might get shuffled between renders (e.g. search results)
The value appended must be the loop index variable (i), not a fixed number — 'person_' + i gives every item a unique key (person_0, person_1, ...); 'person_' + 1 would give every item the same key, defeating the purpose.
React.createElement()
JSX isn't required to write React — every JSX element is compiled into a call to React.createElement(type, props, children).
// JSX:
const h1 = <h1>Hello world</h1>;
// What it compiles to:
const h1 = React.createElement(
"h1",
null,
"Hello world"
);
Easy to miss on the first attempt: all three arguments (and their commas) are required — use null for props when there are none.
Common mistakes
- Forgetting the slash on a self-closing tag — <br> instead of <br />
- Passing {fn()} (calls immediately) when you meant {fn} (passes the reference) for an event handler — or vice versa when you actually need the return value
- Quoting a variable attribute value instead of using curly braces, or vice versa for string literals
- Forgetting the ! negation in an && condition, inverting the intended logic
- Using a fixed number instead of the map index (i) when building unique keys
- Missing a comma or the null placeholder in React.createElement()
Your First React Component
A component is a small, reusable chunk of code responsible for one job — usually rendering some HTML and re-rendering it when data changes. Modern function components are just JavaScript functions that return JSX.
The Two Core Files
| File | Role |
|---|---|
| App.js | Top level of the application — components are usually defined here. |
| index.js | Entry point — imports the app's components and renders them into the actual HTML page. |
Imports You'll Need
| Import | From | Purpose |
|---|---|---|
| import React from 'react'; | react | Not required just to write JSX in modern React, but still imported when a component needs other features (e.g. hooks) from the core library. Deals only with React itself — never touches the DOM. |
| import { createRoot } from 'react-dom/client'; | react-dom/client | Connects a React application to an actual DOM element. Lives in index.js, since that's the entry point. |
Defining a Function Component
function MyComponent() {
return <h1>Hello world</h1>;
}
export default MyComponent;
| Rule | Why |
|---|---|
| Name must start with a capital letter (PascalCase) | Not just a style convention — required. A lowercase first letter makes React look for a built-in HTML tag (like div) instead of your component, and it fails. |
| Must contain a return statement | The function must return a JSX element — that's the instruction telling React what to render. No return, nothing to display. |
| No trailing ; needed after the closing } | A function declaration doesn't need one (unlike a function expression, e.g. const x = () => {};). Adding one anyway is harmless — just unnecessary. |
Default vs Named Exports/Imports
This is the single easiest thing to get tangled up on when a file has more than one component. The two forms are not interchangeable.
| Form | Meaning |
|---|---|
| export default MyComponent; | Default export — a file can only have one. The importer can name it whatever they like. |
| export function Other() {...} | Named export — a file can have many. The importer must use curly braces and the exact same name. |
| import MyComponent from './App'; | No braces — grabs the file's default export, regardless of what name is used here. |
| import { Other } from './App'; | Braces — grabs the named export called Other specifically. Name must match. |
| import MyComponent, { Other } from './App'; | Combined — one default import plus one or more named imports, on a single line. |
Gotchas actually hit while learning this:
1. Two `export default` statements in the same file → syntax error.
Only one default export is allowed per file. A second component
needs a named export instead (`export function Other() {}`), or
its own separate file.
2. `import Other from './App';` (no braces) when Other is a NAMED
export → doesn't error, but silently imports the DEFAULT export
again under a different local name. Symptom: both components
render identical content, because both variables point at the
same default export underneath.
Rule of thumb: braces = "give me the thing with this exact name."
No braces = "give me whatever the default is, I'll call it what I want."
Rendering the Component
import { createRoot } from 'react-dom/client';
import MyComponent from './App';
createRoot(document.getElementById('app')).render(<MyComponent />);
createRoot() takes a real DOM element (usually looked up by id) and turns it into a React root. The root's .render() method is then called with the JSX to display — this can be chained in one line, as above, or split into const root = createRoot(...) followed by root.render(...) on its own line.
Rendering More Than One Component
.render() can only take one root JSX element — multiple sibling components need a wrapper.
createRoot(document.getElementById('app')).render(
<div>
<MyComponent />
<MySecondComponent />
</div>
);
A <>...</> Fragment does the same job without adding an extra DOM node — worth knowing exists, but a plain <div> wrapper works fine and doesn't require learning a new concept early on.
Important: nesting one component inside another — <MyComponent><Other /></MyComponent> — is valid JSX syntax, but the nested content only actually appears on screen if MyComponent is written to accept and render props.children. Without that, whatever's nested inside is silently ignored — no error, it just never shows up. Two unrelated, independent components should be rendered as siblings (wrapped together), not nested.
Common Mistakes
- Naming a component starting with a lowercase letter — React tries to treat it as an HTML tag and fails
- Two export default statements in one file — only one default export allowed per file
- Using a bare (no-brace) import for something that's actually a named export — silently imports the default again instead of erroring
- Expecting a component to render nested children automatically without it using props.children
- Passing multiple sibling elements to .render() without a single wrapping element
Review
- react is imported when a component needs core React features; react-dom/client is imported to connect to the actual DOM
- Function components are plain JS functions, named in PascalCase, that return JSX
- Components are exported from the file that defines them and imported wherever they're used — default exports are unnamed and singular per file, named exports use exact matching names in braces
- createRoot(domElement).render(<Component />) is what actually puts a component on the screen
- Multiple top-level components being rendered together need a single wrapping element
Components and Advanced JSX
Putting the pieces together — multi-line returns, variables and logic inside a component, conditional rendering, and event handlers.
Multi-line JSX Needs Parentheses
A single-line return doesn't need them — return <h1>Hello world</h1>; is fine as-is. The moment the JSX spans more than one line, it must be wrapped in ( ).
function QuoteMaker() {
return (
<blockquote>
<p>The world is full of objects...</p>
<cite>
<a target="_blank" href="...">Douglas Huebler</a>
</cite>
</blockquote>
);
}
Technically this only breaks if a line break comes right after the return keyword — JavaScript's automatic semicolon insertion (ASI) then inserts a semicolon right after return, silently turning it into return; followed by dead code. If the opening tag stays on the same line as return, it technically still works without parentheses — but wrapping in parentheses is the safe, expected convention and what checkpoints/autograders check for.
Logic Before the Return Statement
A function component can contain more than just a return statement — variable declarations and calculations can happen first, then get used inside the JSX that follows.
function RandomNumber() {
// logic first
const n = Math.floor(Math.random() * 10 + 1);
// then the return, using that logic
return <h1>{n}</h1>;
}
Common mistake — logic can't live INSIDE the return's parentheses:
function RandomNumber() {
return (
const n = Math.floor(Math.random() * 10 + 1); // syntax error
<h1>{n}</h1>
);
}
The const declaration must come BEFORE return, never inside it.
Conditional Rendering with if/else
Distinct from the ternary / && approaches covered earlier (see Advanced JSX) — here the if/else lives inside the component, before any JSX, and each branch returns a completely different element outright.
function TonightsPlan() {
if (fiftyFifty) {
return <h1>Tonight I'm going out WOOO</h1>;
} else {
return <h1>Tonight I'm going to bed WOOO</h1>;
}
}
if (fiftyFifty) and if (fiftyFifty === true) behave identically when the value is already a boolean — the === true is redundant but not wrong.
Event Handlers
Defined as a regular function inside the component, then passed to a JSX element as a prop-like attribute (props are covered properly in a later lesson). Convention: name the handler handle + the event type, e.g. handleClick.
function SubmitButton() {
function handleClick() {
alert('Submission Successful.');
}
return <button onClick={handleClick}>Submit</button>;
}
No parentheses on the handler when passing it — onClick={handleClick} passes the function itself, so React calls it only once the click actually happens. onClick={handleClick()} would call it immediately, during render, which is almost never what's wanted.
Common Mistakes
- Multi-line JSX without wrapping parentheses — safe only if the opening tag stays on the same line as return, fragile otherwise
- Putting a variable declaration inside the return statement's parentheses instead of before it
- Reaching for a ternary/&& when what's actually needed is two full, separately-returned elements via if/else
- Passing an event handler as handleClick() instead of handleClick, triggering it immediately instead of on the event
Review
- Multi-line JSX returns should be wrapped in parentheses
- Variables and calculations can be declared inside a component, before the return statement, and then used within the returned JSX
- Components can return entirely different JSX elements based on an if/else placed before the return
- Event handler functions are defined inside the component and passed to JSX elements by reference — no parentheses — so React calls them only when the event fires
Creating a React App with Vite
Vite (pronounced "veet") is the modern, standard way to scaffold a React project locally — it replaces the now-sunsetted create-react-app. Requires Node v18+ or v20+ (check with node -v).
Create the Project
npm create vite@latest
# or, specifying options directly:
npm create vite@latest my-react-app -- --template react
Interactive prompts ask for a project name, framework (React), and variant (JavaScript or TypeScript).
Run It
| Command | What it does |
|---|---|
| cd my-react-app | Move into the newly scaffolded project folder. |
| npm install | Installs all dependencies listed in package.json — required once before the app can run. |
| npm run dev | Starts Vite's local development server, by default at http://localhost:5173/. This is the command that actually runs the app — it must stay running in that terminal tab while you work. Source changes hot-reload straight into the browser without a manual refresh. |
Note: unlike the Codecademy in-browser exercises, nothing shows up
just by saving a file — `npm run dev` has to be running in a terminal
for the app to actually be served anywhere. Leave that terminal tab
open; open a second tab for any other commands.
Key Files & Folders
| Path | Purpose |
|---|---|
| src/main.jsx | The entry point — same role as index.js in the Codecademy lessons (renders the root component into the DOM). Vite names it main.jsx by default. |
| src/App.jsx | The main/top-level component, same role as the App.js used throughout the earlier lessons. |
| src/assets/ | Images and other static assets that get processed by Vite's build. |
| public/ | Static files served as-is, unprocessed (e.g. favicon) — copied directly into the production build. |
| index.html | Root HTML file — Vite injects the bundled JS into this during build. |
| package.json | Lists dependencies and scripts — dev, build, preview are the key ones. |
| vite.config.js | Vite's own configuration file (plugins, aliases, proxy settings, etc). |
| node_modules/ | Installed dependencies — never edited directly, already git-ignored. |
Common Mistakes
- Forgetting to run npm install before npm run dev — the app won't start without dependencies installed first
- Closing the terminal tab (or hitting Ctrl+C) running npm run dev and then wondering why localhost:5173 stops loading — the dev server has to stay running
- Editing main.jsx when a lesson refers to "index.jsx" — Vite's default template names the entry file main.jsx; same file, different name
Components Render Other Components
What makes React powerful isn't any single component — it's how components reference and render each other. A component returning another component's JSX tag is treated as a parent/child relationship.
import Button from './Button';
function App() {
return <Button />;
}
export default App;
This is the same import/export/return pattern already used throughout earlier lessons — the pattern just gets a name here: breaking an app into smaller components, each in its own file, and composing them together by importing and returning them from a parent component. Smaller pieces are easier to reason about individually and can be reused wherever needed.
// NavBar.js
function NavBar() {
return <nav>...</nav>;
}
export default NavBar;
// ProfilePage.js — NavBar is a CHILD of ProfilePage here
import NavBar from './NavBar';
function ProfilePage() {
return (
<div>
<NavBar />
<h1>All About Me!</h1>
</div>
);
}
export default ProfilePage;
Review
- A React app can contain many components, spread across many files
- Components interact by importing and returning instances of each other
- This lets components be broken into smaller pieces, kept in separate files, and reused wherever needed
Props
Props are information passed from a parent component to a child — React's version of function arguments. Props always flow one direction: top to bottom, parent to child. A child can't pass props back up or modify the props it receives — they're read-only.
Passing and Accessing props
// Passing — give the component instance an attribute:
<Greeting name="Jamel" />
<Greeting myInfo={["Astronaut", "Narek", "43"]} /> // non-strings need curly braces
// Accessing — via the props parameter:
function Greeting(props) {
return <h1>{props.name}</h1>;
}
// Or destructured directly in the parameter list:
function Greeting({ name }) {
return <h1>{name}</h1>;
}
String values use plain quotes (name="Jamel"); anything that's actually JavaScript — numbers, booleans, arrays, objects, variables — needs curly braces (age={56}, haunted={true}).
Using props to Make Decisions
A prop doesn't have to be displayed directly — it can drive which JSX gets returned instead.
function LoginMsg(props) {
if (props.password === 'a-tough-password') {
return <h2>Sign In Successful.</h2>;
} else {
return <h2>Sign In Failed..</h2>;
}
}
Passing Event Handlers as props
Functions can be passed as props just like any other value — commonly used to let a child component (e.g. a button) trigger behavior defined in its parent.
// Talker.js (parent) — defines the handler, passes it down
function Talker() {
function handleClick() {
alert('blah '.repeat(10000));
}
return <Button onClick={handleClick} />;
}
// Button.js (child) — receives it, attaches it to a real element
function Button(props) {
return <button onClick={props.onClick}>Click me!</button>;
}
| Convention | Rule |
|---|---|
| Event handler name | handle + event type, e.g. handleClick, handleHover. Just a naming convention for readability — not required by React. |
| Prop name | on + event type, e.g. onClick, onHover. Also just convention — any valid identifier would technically work. |
The capitalization trap — same word, two different meanings:
<button onClick={props.onClick}> lowercase tag = real HTML element
→ onClick is special: React wires
up an actual browser click listener
<Button onClick={handleClick} /> capitalized tag = your own component
→ onClick here is just an ordinary
prop name/key — React does nothing
special with it. It only becomes a
real listener once THAT component
passes it down to a lowercase
element internally.
The rule is about the element's capitalization, NOT about whether the
attribute name "looks like" an event. A made-up name like `bananaSplit`
on a lowercase <button> does NOT create a listener — it must be a name
React actually recognizes (onClick, onChange, onKeyUp, etc.) AND sit on
a real DOM element for it to mean anything special.
Real React/DOM Event Prop Names
Only these (and similar recognized DOM events) trigger real listeners when placed on a lowercase element. Full reference: React docs — common events or MDN — Event reference.
| Category | Prop names |
|---|---|
| Mouse | onClick, onDoubleClick, onMouseDown, onMouseUp, onMouseEnter, onMouseLeave, onMouseMove, onMouseOver, onMouseOut, onContextMenu |
| Keyboard | onKeyDown, onKeyUp (onKeyPress is deprecated) |
| Form / Input | onChange, onInput, onSubmit, onFocus, onBlur, onSelect, onInvalid |
| Touch | onTouchStart, onTouchMove, onTouchEnd, onTouchCancel |
| Clipboard | onCopy, onCut, onPaste |
| Drag & Drop | onDrag, onDragStart, onDragEnd, onDragEnter, onDragLeave, onDragOver, onDrop |
| Scroll / Wheel | onScroll, onWheel |
props.children
Every props object has a children property — it holds whatever was placed between a component's opening and closing tags. A component only renders that nested content if it explicitly places {'{props.children}'} somewhere in its own return statement — nesting alone does nothing on its own.
function List(props) {
return (
<div>
<h2>{props.title}</h2>
<ul>
{props.children}
</ul>
</div>
);
}
// Usage:
<List title="Living Musician">
<li>Sonny Rollins</li>
</List>
Easy-to-miss detail: if a component has more than one child between its tags, props.children is an array. If it has exactly one child, props.children is that single element directly — not wrapped in an array. This is checkable with props.children instanceof Array, which is exactly how a component could decide whether to pluralize a title based on how many children it received.
Default Values for props
// Default in the parameter list (destructured):
function Example({ text = 'This is default text' }) {
return <h1>{text}</h1>;
}
// Or default inside the function body:
function Example(props) {
const { text = 'This is default text' } = props;
return <h1>{text}</h1>;
}
The default only applies when the prop is undefined — i.e. nothing was passed at all. Explicitly passing text="" still counts as passing something, so it overrides the default and renders empty, rather than falling back to the default text.
Common Mistakes
- Forgetting curly braces on a non-string prop value (numbers, booleans, arrays, objects, variables)
- Assuming onClick (or any on... name) is special on a capitalized component — it's only special on a lowercase/native element, and only if the name matches a real recognized event
- Inventing an attribute name and expecting it to create a listener on a native element — the name must match a real DOM event React recognizes
- Expecting nested JSX to render automatically without the component using {'{props.children}'}
- Assuming props.children is always an array — it's a single element (unwrapped) when there's exactly one child
- Trying to mutate a prop directly — props are read-only; new values must come from the parent
Review
- Props pass data one-way, parent to child, and are read-only
- Pass with an attribute (<Comp prop="value" />), access with props.propName or destructuring
- Props can drive conditional rendering, not just display values directly
- Event handlers can be passed as props — reference only, no parentheses, so React calls them on the event rather than immediately
- handleX / onX naming is convention for readability; only a real event name on a lowercase/native element actually creates a browser listener
- props.children holds whatever's nested between a component's tags — single child unwrapped, multiple children as an array
- Default prop values can be set via destructuring — they apply only when the prop is undefined