Components and props

Function components, JSX rules, prop passing with destructuring, children, and the keys rule for every list you render.

A component is a function

function Badge({ label, tone = 'neutral' }) {
  return <span className={'badge badge-' + tone}>{label}</span>
}

// arrow form, when you prefer it
const Title = ({ children, level = 2 }) => <h2 className="title">{children}</h2>

function App() {
  return (
    <>
      <Title>Releases</Title>
      <Badge label="stable" tone="good" />
    </>
  )
}
  • Component names are capitalised; lowercase tags are treated as HTML elements rather than your components.
  • A component must return a single root — a fragment (<>…</>) groups siblings without adding a DOM node.
  • JSX attributes use camelCase DOM properties: className, htmlFor, onClick, tabIndex.
  • Curly braces switch from markup to JavaScript; anything that evaluates to a string, number, element, array of elements, or null can be rendered.
  • {' '} or a template-free concatenation is how you insert a literal space between two expressions.
ExperienceWhere to put it
Needs state or handlersA component (function)
Pure formatting of one valueA helper function, not a component
Repeated markup inside a fileA local component above the one that uses it
Reused across routesIts own file, imported by name
Wraps layout onlyA component that renders {children}

Props are read-only

function Price({ amount, currency = 'USD', onSelect }) {
  return (
    <button onClick={() => onSelect(amount)}>
      {currency} {amount.toFixed(2)}
    </button>
  )
}

// pass a value, a function, or an element
<Price amount={19.5} currency="EUR" onSelect={addToCart} />
<Card header={<Title>Invoice</Title>} />
  • Props flow down and must never be assigned inside the child. If the child needs to change a value, the parent owns it — pass a callback and lift the state up.
  • Destructure in the parameter list so defaults and required props are visible at the top of the file.
  • children is an ordinary prop: whatever sits between the opening and closing tag.
  • Spreading ({...rest}) onto a DOM element forwards unknown attributes, but validate that you are not forwarding internal props as invalid HTML attributes.
  • A component re-renders when its props or its own state change; there is no per-prop watcher to configure.
💡
Composition beats configuration. A Card that accepts a header, footer and body prop will grow forever, while one that accepts children plus a named slot-like prop adapts to layouts you have not imagined yet.

Lists and keys

function GuideList({ guides }) {
  return (
    <ul>
      {guides.map(guide => (
        <li key={guide.id}>
          <a href={'/guides/' + guide.slug}>{guide.title}</a>
        </li>
      ))}
    </ul>
  )
}

The key is React's identity tag for an element between renders. It must be stable and unique among siblings — a database id, or a slug. Using the array index (key={i}) is only safe when the list never reorders, never filters and never grows in the middle.

Key choiceReorderInsert at topVerdict
key={item.id}CorrectCorrectUse this
key={i}Wrong rows keep stale stateEvery row re-rendersAvoid for editable or sortable lists
key={Math.random()}Whole list remountsWhole list remountsNever — kills performance and loses focus

A wrong key shows up as a specific class of bug: a controlled input in a row keeps the text typed into a different row, or a checkbox stays checked after the data moves. The state lives in the element's position, not in the data.

FAQ

Should I use defaultProps or default parameters?
Default parameters ({ tone = 'neutral' }) are the modern approach for function components; defaultProps is legacy and no longer applies to function components.
Why not just use index keys everywhere?
It works until the list reorders, filters or gains an item in the middle — then React matches the wrong element to the wrong state. Reproduce it once and you will not do it again.

State with hooks JavaScript basics

Last refreshed 2026-09-18.