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

  1. The entire virtual DOM gets updated.
  2. The virtual DOM is compared (diffed) against a snapshot of what it looked like before the update — React figures out which objects changed.
  3. The changed objects, and only those objects, get updated on the real DOM.
  4. 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 itonClick={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