Skip to the chapter
←ReactGGroundwork⚛React
React/Beginner/B4
4 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛B4 · Part 1 · Beginner

Props

Data flows down. A component can read its props and must never write to them.

4 min read12 sections3 exercises
4 of 57

In this chapter

  • Props are the function's arguments
  • Destructure in the signature
  • Props are read-only
  • Changes go up as callbacks
  • Spreading, and when not to

and 7 more

about 4 min
12On this page
  1. Props are the function's arguments
  2. Destructure in the signature
  3. Props are read-only
  4. Changes go up as callbacks
  5. Spreading, and when not to
  6. Passing elements and functions
  7. Two props React takes for itself
  8. children is more flexible than it looks
  9. Naming that survives contact with other people
  10. An object prop is a new reference every render
  11. Checking props without TypeScript
  12. A prop that changes is not a variable you can watch

Props are the function's arguments

Everything you write as an attribute becomes a key on one object, and that object is the component's only argument.

<Avatar name="Ana" size={48} active />

// arrives as
{ name: "Ana", size: 48, active: true }

A bare attribute with no value is true. Anything that is not a string needs braces — size="48" passes the string "48", size={48} passes the number.

Destructure in the signature

function Avatar({ name, size = 40, active = false }) {
  return (
    <img
      src={"/avatars/" + name + ".png"}
      width={size}
      className={active ? "is-active" : ""}
      alt={name}
    />
  );
}

Defaults go in the destructuring. There is no separate mechanism for them, and the old Component.defaultProps is gone in React 19.

Props are read-only

This is not a convention that is nice to follow. It is the rule the whole model rests on.

function Total({ items }) {
  items.push({ id: 99 });   // ✗ mutates the parent's array
  items = [...items, x];    // ✗ pointless: reassigns a local
  const next = [...items, x];  // ✓ a new array of your own
}

A component that mutates a prop is changing data the parent owns, without the parent knowing. React will not re-render the parent, so the screen and the data disagree — and the bug appears somewhere else entirely.

One-way data flow

Data goes down through props; changes go up through callbacks. The component that owns a piece of state is the only one allowed to change it. Everyone below receives the value and a function to ask for a change.

Changes go up as callbacks

function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} onIncrement={() => setCount(count + 1)} />;
}

function Child({ count, onIncrement }) {
  return <button onClick={onIncrement}>{count}</button>;
}

Child cannot change count. It can only say "the button was pressed", and the parent decides what that means. That indirection is what makes a component reusable in a context its author never saw.

Spreading, and when not to

<Avatar {...user} />                    // every key of user becomes a prop
<Avatar {...user} size={64} />          // later wins: size is 64
<Avatar size={64} {...user} />          // user.size wins, if it has one

Spreading is genuinely useful in one place: a wrapper that forwards the props it does not care about to the element underneath.

function Button({ variant = "primary", ...rest }) {
  return <button className={"btn btn--" + variant} {...rest} />;
}

<Button variant="ghost" type="submit" disabled onClick={save}>Save</Button>

Everywhere else it hurts, because you can no longer see what a component receives by reading the call. <Chart {...props} /> tells the next reader nothing.

Passing elements and functions

Props are not limited to strings and numbers. Anything a JavaScript value can be, a prop can be — including elements and other components.

<Modal title={<strong>Delete?</strong>} onConfirm={remove} />
<List items={rows} renderItem={(row) => <Row key={row.id} {...row} />} />
<Route element={<Dashboard />} />

Two props React takes for itself

<Row key={item.id} ref={rowRef} item={item} />

key and ref look like props and are not. React strips both before your component is called — key is for list identity, ref for reaching a DOM node. Neither appears in props.

In React 19 ref is finally a normal prop for function components, so forwardRef is no longer needed for new code. You will still meet it constantly in existing codebases.

children is more flexible than it looks

<Wrapper>text</Wrapper>              // children: "text"
<Wrapper><A /></Wrapper>            // children: one element
<Wrapper><A /><B /></Wrapper>       // children: an array
<Wrapper>{(x) => <A v={x} />}</Wrapper> // children: a function

The last one is the render prop pattern — the wrapper owns some state and hands it to the caller to render. Hooks replaced most uses of it, but you will still find it in libraries where the wrapper needs to control when and how often the children render.

Naming that survives contact with other people

KindConventionExample
Booleanis/has/can, and positiveisOpen, not isNotClosed
Handler propon + what happenedonSelect, onDismiss
The function itselfhandle + what happenedhandleSelect
Element propthe slot's nameicon, footer

Name handlers after what happened, not what should occur. onDelete ties the child to one outcome; onConfirm lets the parent decide what confirming means.

An object prop is a new reference every render

<Chart options={{ grid: true }} />          // new object every render
<Chart onZoom={() => zoom(1)} />           // new function every render

Harmless most of the time — creating a small object is cheap. It starts to matter in exactly two places: when the child is memoised, where a new reference defeats the memo entirely; and when the prop ends up in a dependency array, where it makes an effect run on every render. Both are covered later, but the shape is worth recognising now.

Checking props without TypeScript

PropTypes was removed from React in version 19. In a plain JavaScript project the remaining options are a runtime check at the top of the component, or JSDoc types that your editor understands:

/** @param {{ items: string[], onPick: (s: string) => void }} props */
function Picker({ items, onPick }) { ... }

The real answer is TypeScript, which is what the intermediate tier covers — props are a function's parameters, so typing them is typing a function.

A prop that changes is not a variable you can watch

A common early instinct is to copy a prop into state so it can be "kept in sync". Almost always wrong — the copy goes stale the moment the prop changes, and now there are two sources of truth for one value.

function Price({ cents }) {
  const [display, setDisplay] = useState(cents / 100);  // ✗ frozen at first render
  const display = cents / 100;                          // ✓ just compute it
}

If a value can be derived from props, derive it during render. State is for things the component owns, which is the subject of the next chapter.

Interview answer, one sentence

"Props are a component's arguments and they are immutable from the inside. Data flows down as props and changes flow up as callbacks, so the component that owns a value is the only one that can change it — which is what makes a React tree traceable."

03Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsA Badge with sensible defaultsbeginner
  • 7 testsA prop with a sensible defaultbeginner
  • 7 testsDestructure the object you were handedbeginner
All 46 for this level →
B4

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. B3Previous · B3Components & composition
  2. B4you are here
  3. Next · B5State with useStateState is memory between renders — and setting it is a request, not an assignment.5 min read

01On this page

12 sections · 0%

  1. 01Props are the function's arguments
  2. 02Destructure in the signature
  3. 03Props are read-only
  4. 04Changes go up as callbacks
  5. 05Spreading, and when not to
  6. 06Passing elements and functions
  7. 07Two props React takes for itself
  8. 08children is more flexible than it looks
  9. 09Naming that survives contact with other people
  10. 10An object prop is a new reference every render
  11. 11Checking props without TypeScript
  12. 12A prop that changes is not a variable you can watch

[] chapters · t top