- Home
- /
- Tutorials
- /
- React Tutorial
- /
- How React Thinks
React Introduction
How React Thinks
Most React confusion is not about syntax. It is about not yet believing that the component function runs again, from the top, every single time.
The component function re-runs
When state changes, React calls your component function again. Every line inside it runs again. Local variables are recreated. This is the single fact that explains the most surprising React behaviour.
Watch it re-run
jsx
function Rerun() {
const [count, setCount] = React.useState(0)
// This runs on every render, so the list grows as you click.
const renderedAt = new Date().toLocaleTimeString()
return (
<div>
<p>Count: {count}</p>
<p>This render happened at {renderedAt}</p>
<button onClick={() => setCount(count + 1)}>Re-render</button>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<Rerun />)Click the button and the timestamp changes. Nothing told it to - the whole function ran again because state changed.
Describing, not instructing
In plain JavaScript you write imperative code: a sequence of steps that mutate the page. In React you write declarative code: an expression of what the page should look like given the current data.
- State changes.
- React calls your component function again.
- It compares the new description with the previous one.
- It applies only the differences to the real DOM.
Step four is why React is fast enough to re-run everything: the expensive part is touching the DOM, and React touches very little of it.
Data flows one way
Data moves from a parent down to its children through props. A child cannot reach up and change its parent's data. If a child needs to cause a change, the parent passes down a function for it to call.
This feels restrictive at first and is the reason React apps stay debuggable. When a value is wrong you walk up the tree to find where it came from, and there is only ever one path.
Data down, events up
jsx
function Child({ label, onPress }) {
// The child receives data and a function. It owns neither.
return <button onClick={onPress}>{label}</button>
}
function Parent() {
const [presses, setPresses] = React.useState(0)
return (
<div>
<p>Pressed {presses} times</p>
<Child label="Press me" onPress={() => setPresses(presses + 1)} />
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<Parent />)Child knows nothing about presses. It is handed a label and a function, and calling that function is the only influence it has.
What this buys you
- A component can be read on its own - its inputs are its props and its state, nothing else.
- The same inputs always produce the same output, which makes components testable.
- Bugs have a direction: an incorrect value came from somewhere above.
