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
nullcan be rendered. {' '}or a template-free concatenation is how you insert a literal space between two expressions.
| Experience | Where to put it |
|---|---|
| Needs state or handlers | A component (function) |
| Pure formatting of one value | A helper function, not a component |
| Repeated markup inside a file | A local component above the one that uses it |
| Reused across routes | Its own file, imported by name |
| Wraps layout only | A 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.
childrenis 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 choice | Reorder | Insert at top | Verdict |
|---|---|---|---|
key={item.id} | Correct | Correct | Use this |
key={i} | Wrong rows keep stale state | Every row re-renders | Avoid for editable or sortable lists |
key={Math.random()} | Whole list remounts | Whole list remounts | Never — 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.
Related
State with hooks JavaScript basics
Last refreshed 2026-09-18.