Today’s goal
Understand what happens between a state change and the UI the user sees.
We will connect:
- state-driven rendering;
- snapshots, batching, and identity;
- reconciliation and keys;
- derived values and memoization;
- effects and cleanup;
- React and Vue mental models;
- fine-grained reactivity and signals;
- scheduling, measurement, and unnecessary work.
By the end of today you can
- describe UI as a function of state;
- distinguish render calculation from DOM commitment;
- explain reconciliation and component identity;
- use keys to preserve or reset state intentionally;
- update from previous state safely;
- separate source state from derived values;
- reserve effects and watchers for external synchronization;
- explain React snapshots, Vue proxies, computed values, and batching;
- trace a reactive dependency graph;
- optimize only after measuring the actual work.
Reactivity does not mean everything runs automatically
A reactive system must decide:
- what depends on what;
- what work is invalidated;
- when work is scheduled;
- what can be reused;
- what must be synchronized;
- when cleanup runs.
“Reactive” describes a mechanism, not a guarantee that every operation is free or immediate.
State is not the same as every variable
const label = "Products"; // stable configuration
let localCounter = 0; // ordinary mutable variable
const [query, setQuery] = useState(""); // render-relevant state
Use state when a change must participate in the UI’s update model.
Do not put every value in state just because it changes somewhere.
Virtual DOM without the mythology
“Virtual DOM” is a representation and comparison strategy.
It does not mean:
- the entire real DOM is rebuilt every time;
- virtual work is automatically faster than all alternatives;
- every render creates a visible browser update;
- architecture no longer matters.
Measure the work that actually matters.
Parent rendering and child rendering
When a parent renders, its child functions may be called again.
That does not automatically mean:
- every child DOM node changed;
- every child state reset;
- every expensive calculation must rerun forever.
Rendering, reconciliation, commitment, and state preservation are related but distinct questions.
Events and effects answer different questions
event: what should happen because the user did this?
effect: what external system must be synchronized with committed state?Submit an order because the user activated submit.
Update a subscription because committed state says the subscription should exist.
Do not turn every event response into an effect chain.
Effect cleanup prevents stale work
useEffect(() => {
const controller = new AbortController();
loadProducts(query, controller.signal);
return () => controller.abort();
}, [query]);Cleanup runs when dependencies change or the component leaves the tree.
It prevents old subscriptions, timers, and requests from outliving the state that created them.
Vue rendering is a reactive effect
When a component renders, Vue records which reactive values it reads.
When one of those values changes, Vue knows that the component’s rendered output may need updating.
This is dependency tracking at the property level rather than a blanket statement that every component always reruns.
Vue batching reduces intermediate work
Several synchronous mutations can be grouped into one update cycle.
This is why code that needs the updated DOM may need nextTick() or an equivalent lifecycle boundary.
The scheduling model should be part of the component’s reasoning, especially around focus and measurement.
Watcher cleanup prevents stale effects
watch(query, async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
onCleanup(() => controller.abort());
await loadProducts(value, controller.signal);
});If a newer query arrives, the old operation should not win after it becomes irrelevant.
React and Vue share the goal, not the mechanism
| Concern | React | Vue |
|---|---|---|
| primary model | component calculation | tracked reactive dependencies |
| derivation | expression / memo | computed |
| external sync | effect | watch / watchEffect |
| state update | setter schedules render | mutation invalidates dependencies |
| DOM timing | commit and effects | queued update and nextTick |
Learn the mechanism well enough to predict behavior; do not flatten the differences into slogans.
Component memoization is not a correctness fix
const ProductCard = memo(function ProductCard(props: Props) {
return <article>{props.product.title}</article>;
});Memoization can skip a render when props are considered equal.
It cannot repair incorrect keys, duplicated state, stale closures, or a bad ownership boundary.
State preservation is architectural
When state disappears unexpectedly, ask:
- did the component type change?
- did its position change?
- did its key change?
- did a conditional branch replace its identity?
- did the data identity change while the key stayed index-based?
The answer is often in the render tree, not in the state setter.
Compiler-assisted optimization
Compilers can sometimes infer:
- stable expressions;
- memoization opportunities;
- dependency relationships;
- component boundaries;
- update paths.
They cannot infer product intent, correct identity, or whether a value belongs in state.
Optimization tools reduce some manual work; they do not remove architectural decisions.
React Compiler is an example, not a new mental model
Compiler assistance may reduce the need for some manual memoization.
The fundamentals remain:
- pure render calculations;
- stable identity;
- correct dependencies;
- explicit effects;
- measured performance work.
Understand the behavior even when tooling automates an optimization.
React anti-pattern: derived state effect
useEffect(() => {
setFilteredProducts(filterProducts(products, query, filter));
}, [products, query, filter]);This creates:
- duplicated state;
- an extra render path;
- a possible stale intermediate value;
- more code to test and debug.
Calculate the list directly unless there is a measured reason not to.
Trace React updates
For a state change, record:
- Which setter was called?
- Which snapshot created the handler?
- Which components calculate again?
- Which identities and keys are reused?
- Which host nodes commit changes?
- Which effects run and which clean up?
This sequence is more useful than saying “React rerendered everything.”
Trace Vue updates
For a reactive mutation, record:
- Which ref or proxy property changed?
- Which computed values depend on it?
- Which component render effects read it?
- Which watchers are triggered?
- When is the DOM queue flushed?
- Which cleanup functions run?
The goal is to expose the dependency graph and schedule.
What should we measure?
Measure questions such as:
- how long does the expensive calculation take?
- how many items are processed?
- how often does the calculation run?
- how many components commit changes?
- does input responsiveness degrade?
- does memory grow because of caches or subscriptions?
Choose a measurement that can change the decision.
State location affects rendering scope
State placed high in the tree can coordinate many consumers but broaden update scope.
State placed close to one interaction can reduce unrelated work but may require a deliberate communication path.
Choose location based on ownership and synchronization - not on a universal rule to lift or localize state.
Do not let the reactive system become a mystery network
A healthy graph has:
- visible sources;
- named derivations;
- few synchronization edges;
- bounded effects;
- explicit cleanup;
- tests for identity and timing.
If changing one field triggers an unexplained chain of watchers and effects, simplify the graph before optimizing it.
A practical decision model
Ask:
- Is another value directly calculable from this one?
- Does a user action cause an operation?
- Must an external system remain synchronized?
- Is a calculation expensive and repeated unnecessarily?
- Is the update scope too broad?
These questions map naturally to derived values, events, effects, memoization, and state placement.
Four reactive strategies
| Strategy | Main strength | Main risk |
|---|---|---|
| React render model | explicit component calculation | confusing render with DOM work |
| Vue dependency tracking | selective property updates | hidden reactive connections |
| signals | fine-grained graph | graph and lifecycle complexity |
| compiler assistance | less manual optimization | false confidence about architecture |
Use the model your team can explain and debug.
Practical stages 4–5: cycles and comparisons
- Create a cycle deliberately and explain why production systems must guard against it.
- Compare the educational graph with React render calculation and Vue
computed().
Ask which mechanism discovers dependencies, when invalidation occurs, and how cleanup is represented.
Troubleshooting guide (Part 1)
| Symptom | Likely cause |
|---|---|
| State update seems one step behind | Snapshot or batching misunderstood |
| Input state moves to another row | Unstable or index-based keys |
| UI flashes an old derived value | Derived state synchronized through an effect |
| Effect runs forever | Effect updates one of its own dependencies |
Troubleshooting guide (Part 2)
| Symptom | Likely cause |
|---|---|
| Vue value stopped updating | Destructuring removed the reactive connection |
| DOM is old after a mutation | Update is queued; await the framework flush |
| Memoization changes nothing | Dependencies or calculation cost do not justify it |
| Async result wins after a newer query | Missing cleanup or cancellation |
Completion checklist
- source state is distinct from derived values;
- render calculations are free of external side effects;
- list keys represent stable identity;
- previous-state updates are used for dependent changes;
- effects and watchers synchronize external systems only;
- cleanup prevents stale subscriptions and requests;
- React and Vue timing differences are understood;
- optimization decisions are supported by measurement;
- the reactive graph is explainable from source to screen.
Misconceptions to leave behind (Part 1)
| Misconception | Better mental model |
|---|---|
| State changed, so the DOM changed immediately | Updates are calculated and scheduled |
| A React render rebuilt the DOM subtree | Render and commit are different phases |
| Virtual DOM is always faster | Performance depends on the measured work |
| Every rerender is a bug | Some recalculation is necessary |
| Keys only remove warnings | Keys preserve logical identity |
Misconceptions to leave behind (Part 2)
| Misconception | Better mental model |
|---|---|
| Derived values belong in state | Direct calculations usually stay derived |
| Effects respond to any state change | Effects synchronize external systems |
| Vue watchers calculate normal derivations | computed represents derivation |
| Signals are one standard technology | Signals are a family of mechanisms |
| Compiler optimization makes architecture irrelevant | Tools cannot choose ownership or intent |