Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~23 min· topic 5 of 6

Topic 6.5

One-Way Data Flow: Props Down, Events Up

In one line

In React, data travels one way: parents pass values down as props, and children report events up by calling callback props; siblings talk through their shared parent, and passing props through many layers that don't use them is called prop drilling.

Think of it like this

A river and a phone. Water (data) only flows downhill, from the mountain (the parent) to the villages (children). A village can't push water back up the river. But it can phone the dam upstream and ask it to open the gates. In React, props are the river and callback functions are the phone.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

One-way data flow
Data moves only from parents to children, through props.
Read-only props
A component must never change the props it receives. Only their owner can change the data.
Callback prop
A function passed down so a child can report an event, usually named onSomething.
Props down, events up
Values travel down as props; news about events travels up by calling callback props.
Siblings
Components that share the same parent, like a search box and a results table.
Prop drilling
Passing a prop through several components that don't use it, only to reach a deep child.

Step by step

01Build the static tree first

A product page has a SearchBar and a ProductTable. Both depend on the search text and an "in stock only" checkbox. Their closest common parent is FilterableProductTable, so that's where the state goes (Topic 2.7, "Thinking in React").

Build the static tree firstdiagram
Rendering diagram…

02Props down

The parent passes the current values to both children. SearchBar shows them in its inputs; ProductTable uses them to decide which rows to show.

03Events up

The parent also passes setters (or small functions) as callbacks. When you type, SearchBar calls onFilterTextChange(e.target.value). It doesn't know that this changes state in the parent; it only reports what happened.

SearchBar.jsxwhole filejsx
function SearchBar({ filterText, onFilterTextChange }) {
  return (
    <input
      value={filterText}
      placeholder="Search…"
      onChange={(e) => onFilterTextChange(e.target.value)}
    />
  );
}

04The round trip

One key press makes a full loop: the child reports, the parent sets state, React renders the parent, and both children get the new props. The table updates even though it never heard from the search box directly.

The round tripdiagram
Rendering diagram…

05When the owner is far away: prop drilling

If App owns user and only Avatar, four levels down, uses it, then Layout, Sidebar and UserMenu must all accept and forward user. That's prop drilling. Keep it while it's short; Phase 9 removes it when it isn't.

When the owner is far away: prop drillingdiagram
Rendering diagram…

Try it yourself

  1. 1

    Add a third sibling

    In the product example, add a <ResultCount products={products} filterText={filterText} inStockOnly={inStockOnly} /> that shows N products. Predict: does SearchBar need any change? (No. The new sibling reads the same parent state.)

  2. 2

    Pass information, not events

    Change SearchBar to call onFilterTextChange(e) with the whole event, and the parent to do setFilterText(e.target.value). It works, but now the parent knows about input events. Change it back and notice which version makes SearchBar easier to reuse with a different input.

  3. 3

    Shorten the drill with children

    In the drilling example, make Layout and Sidebar accept children, and in App write <Layout><Sidebar><UserMenu user={user} /></Sidebar></Layout>. Which components still mention user? This composition trick is Topic 9.1.

Code & diagrams

Siblings talk through their parent New tab

Type `an` or tick the checkbox. `SearchBar` and `ProductTable` never reference each other; the parent owns the two facts and both children read them.

Preview

Press Run to see the page.

Console

Nothing logged yet.
Props are read-only (the development build enforces it) New tab

React freezes the props object in development, so assigning to a prop throws. Nested objects like `item` are not frozen, which is why mutating `item.name` would silently corrupt the parent's state. Report the change instead, and let the owner copy and update.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

props object frozen? true
item inside it frozen? false
Prop drilling, made visible New tab

Three components accept `user` only to pass it on. Adding a second piece of data (say, `theme`) means editing all of them. Phase 9 fixes this with composition and context.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Layout only forwards user
Sidebar only forwards user
UserMenu only forwards user
Avatar uses user: Asha

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Assign to a prop

Write a component that changes its own prop, function Badge(props) { props.label = props.label.toUpperCase(); return <span>{props.label}</span>; }, and render <Badge label="new" />.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot assign to read only property 'label' of object '#<Object>'
An error occurred in the <Badge> component.
 
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.

Break #2

Mutate a nested prop object

In the read-only example, change the button to onClick={() => { item.name = item.name + "!"; }} and click it.

terminal
$ Preview
── what you'll see ──
Nothing changes on screen, no error, no warning. The parent's data now says "Tea!!!" after three clicks, and it appears only when something else makes the list render.

Myth vs fact

Myth

Children can update their parent's state.

Fact

They can only ask, by calling a function the parent gave them. The parent's own code changes its state.

Myth

Two-way binding would be simpler.

Fact

Two-way binding hides who changed what. One-way flow means every change goes through the owner's setter, so bugs are easy to trace. Controlled inputs give you the convenience with the clarity kept.

Myth

Prop drilling is always bad.

Fact

Passing props two or three levels is explicit and easy to follow. It becomes a problem only when many layers exist just to forward data.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    React freezes element.props (and the element) only in development builds; production skips it for speed, so code that mutates props may "work" in production while being wrong. Treat the dev error as a real bug.

  • ▸

    Passing a state setter directly as a callback (onFilterTextChange={setFilterText}) is fine and stable: React guarantees setter identity doesn't change between renders. Wrapping it ((t) => setFilterText(t.trim())) creates a new function each render, which matters only for memo children (Topic 16.4).

  • ▸

    One-way flow is what lets React DevTools answer "why did this render?" and lets the React Compiler reason about your code: a value can only change where its owner sets it. Flux and Redux (Topic 15.4) scaled the same idea to whole apps with a single store and dispatched actions.

  • ▸

    When a child must notify the parent *and* update its own state, do both in the same event handler. React batches them into one render. Doing the notification in an effect after render causes an extra render pass and a moment of inconsistent UI (Topic 7.6).

Remember this

  1. 1

    One-way data flow (also called unidirectional data flow) means props move only from parent to child. A child receives props and must treat them as read-only: it never changes them. In the development build React even freezes the props object, so props.label = "x" throws an error. Because data only flows down, you can find where any value comes from by walking up the tree to the component that owns it.

  2. 2

    Children still need to say "something happened": a button was clicked, text was typed, an item was deleted. They do it with callback props: functions the parent passes down, named onSomething by convention (onSelect, onDelete, onFilterTextChange). The child calls onDelete(item.id); the parent decides what that means and updates its own state. The new value then flows down again. This is "props down, events up".

  3. 3

    Callback props describe what happened, not what to do: onDelete(id), not removeFromArrayAndSave(id). The child stays reusable (the same TodoItem can be used where deletion means something else), and the owner of the state stays the only place that changes it. Pass the information the parent needs as arguments (an id, the new text), not the whole event object, unless the parent truly needs it.

  4. 4

    Siblings never talk directly. A search box and a results table are siblings; the box can't reach into the table. Instead their parent owns the shared state (the filter text, Topic 6.3), passes it down to both, and gives the search box a callback to change it. Every sibling conversation goes up to the common parent and back down.

  5. 5

    Notify the parent in the event handler, in the same place you update local state, not later in an effect. Then both updates are batched into one render (Topic 4.5) and nothing renders with half-updated data. Topic 7.6 shows the effect version and why it's slower and buggier.

  6. 6

    Prop drilling is what happens when the state's owner is far above the component that needs it: every component in between must accept the prop and pass it on, even though it doesn't use it. For two or three levels this is fine and very clear. When it spreads across many layers it becomes noisy and fragile. Phase 9 shows the fixes: passing components as children (composition, Topic 9.1) and context (Topic 9.2).

Explain it without notes

01

What is one-way data flow, and why does React use it?

02

How does a child component tell its parent that something happened? Show a small example.

03

Two sibling components need the same data. How do they communicate?

04

What is prop drilling, when is it fine, and what are the fixes?

Practice

01

Write a Rating component that shows 5 star buttons and calls onRate(n) when star n is clicked, and a parent that stores the rating and shows You rated N/5.

02

A ColorPicker and a Preview box are siblings. Make choosing a colour in the picker change the preview's background.

Trade-offs

  • ↔

    One-way flow adds wiring (callback props) compared with letting components change shared data directly, but every change has one obvious place where it happens.

  • ↔

    Prop drilling is explicit but verbose; context removes the wiring but hides where data comes from and re-renders every consumer on change (Topic 9.3).

Done when you can

  • I treat props as read-only, including nested objects.

  • I report events up with onSomething callback props that pass information.

  • I connect siblings through their closest common parent.

  • I notify parents in event handlers, not later.

  • I can recognise prop drilling and name the fixes coming in Phase 9.