What is one-way data flow in React?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
Data in React travels in a single direction: a parent passes props down to a child, and the child cannot write to them. To change something, the child calls a function the parent gave it, and the parent updates its own state — which flows back down as new props.
function Parent() {
const [name, setName] = useState('');
return <Input value={name} onChange={setName} />; // data down, events up
}
function Input({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />;
// it never assigns to props.value
}
The state lives in exactly one place. Everything below it is a function of that state.
Why this is worth the extra wiring
- A wrong value has one possible origin. When the UI shows something impossible, you walk up the tree to the owner of that state. With two-way binding, any bound component could have written it.
- Rendering is predictable. Given the same props and state, a component renders the same output, which is what makes it testable without mounting its parents.
- Updates are ordered. React collects state changes and re-renders top-down in one pass, so you never see a half-updated tree or a cascade of watchers triggering each other.
- It matches how you debug. React DevTools shows props flowing down; the Components tree is literally the data flow.
What you give up
More boilerplate for a simple form field: a state variable and a callback where two-way binding needs one line. React's answer is that this is a deliberate trade — explicitness now for debuggability later.
The parts people get wrong
Props are read-only, but props holding an object are only shallowly protected. Mutating props.user.name will not throw and will not re-render; it just leaves you with a value React does not know about.
And "data down" does not mean "state at the top". State belongs at the closest common ancestor of the components that need it — lifting it higher than that makes every unrelated child re-render.
Two-way binding still exists where it is genuinely local: an uncontrolled <input> holding its own DOM value is fine when nothing else needs to read it.
