← React: components, state and interface decisions
02 / 10 · 60 MIN

Events, state snapshots and update queues

Interpret callbacks, captured values and composition of state updates. Distinguish event propagation, default behavior and a delayed action’s contract.

Pass functions and wire components

In onClick={addPair}, the supplied value is a function React can call in response to the event. In onClick={addPair()}, the function is called while the JSX expression is calculated, and its returned value is passed as the prop. The difference concerns timing and contract, not just syntax.

AddPairButton receives a custom prop named onAddPair and explicitly connects it to button’s onClick. The custom name can reflect business intent; it does not create a native event by itself. During reviews, follow the function from the parent to the element receiving the interaction.

A function left unused inside the child explains why an apparently configured button does not produce the expected action.

Propagation and default behavior

A click on a child element can propagate to an ancestor’s handler. stopPropagation can prevent later propagation; it does not undo a capture handler that has already run. preventDefault addresses a different contract: it cancels the default action of a cancelable event, such as normal navigation on form submission.

Do not treat the two operations as synonyms. In a form with onSubmit, preventing navigation may be necessary without preventing another part of the tree from observing the event. For a button inside a clickable card, limiting click propagation may be useful.

The example uses type="button" to avoid implicitly requesting submission when included in a form.

State belongs to the render snapshot

useState supplies this render’s value and a function for requesting an update. After setQuantity(5), reading quantity in the same handler still uses the value captured when that handler was created. No short wait makes that local binding point to another render.

This rule explains why console.log immediately after a setter can show the earlier value without the setter having failed. To analyze an incident, identify the callback’s originating render and the expressions it evaluates. A new render can create another handler with new inputs, but it does not rewrite variables captured by a callback that has already been scheduled.

Replacements and transformations in a queue

A value such as setQuantity(quantity + 1) is calculated before it is passed to the setter. With quantity equal to 3, two such calls request two replacements with 4. An updater n => n + 1 represents a different operation: it receives pending state and returns the next transformation.

Two updaters produce 4 and then 5. Operations can also be combined if order is respected: replace with 10 and then subtract 3 in an updater produces 7; a final replacement with 2 makes the final result 2. To review an excerpt, build a mental table of pending state, operation and result.

Do not assume each queue entry creates an intermediate visible DOM state.

Batching boundaries and updater purity

React can process several updates after a handler finishes, avoiding partial presentation after every call. This does not mean indiscriminately batching separate intentional clicks. An update from the first click can disable a button before another click.

That interface behavior does not prove exactly-once server delivery under retries, failures or other sessions. Keep updaters pure as well: returning n + 1 is a calculation; sending an external message inside the function adds an effect. In development, Strict Mode can call an updater again and discard one result.

The correction is to remove the effect from calculation, not adjust counts for a supposedly fixed number of calls.

Delayed callbacks and the relevant instant

The example’s timer captures quantity from the Record selection click. If the interface changes from 5 to 7 before three seconds elapse, that timer’s note still indicates 5. This meets the requirement in the comment: record the selection from that click.

Another product might require acting on the latest value; that need requires an explicit design, not an assumption that the closure changes by itself. Before fixing an older value, ask which instant constitutes the user’s decision. Multiple timers and out-of-order responses introduce another problem that this small excerpt does not solve.

Cancellation, replacement or identity-based acceptance must be defined when such behavior enters scope.

Diagnose before selecting a change

For an unresponsive button, first check whether the callback reaches the native element. For a quantity that rises by only one, distinguish replacements calculated from the same snapshot from successive transformations. For an older confirmation, compare the captured instant with the functional requirement.

These symptoms have no universal correction involving another timer, Effect or global variable. Keep analysis premises visible: initial state, call sequence, existence of other updates and external effects. This lesson’s cases supply those facts and request a single- or multiple-choice decision.

Reading and justifying behavior is sufficient; running JSX is not required to answer.

import {useState} from 'react';

function AddPairButton({onAddPair}) {
  return <button type="button" onClick={onAddPair}>Add pair</button>;
}

export default function QuantityReview() {
  const [quantity, setQuantity] = useState(3);
  const [note, setNote] = useState('No selection recorded');
  function addPair() {
    setQuantity(n => n + 1);
    setQuantity(n => n + 1);
  }
  function recordSelection() {
    // Contract: record the quantity selected at this click.
    setTimeout(() => setNote(`Selected at click: ${quantity}`), 3000);
  }
  return <>
    <p>Quantity: {quantity}</p>
    <AddPairButton onAddPair={addPair} />
    <button type="button" onClick={recordSelection}>Record selection</button>
    <p>{note}</p>
  </>;
}
IN PRACTICE

Starting from 3, clicking Add pair produces 5 by composing two updaters. If Record selection is clicked at 5 and quantity later changes to 7, that confirmation’s timer retains 5. The excerpt illustrates one observed timer; it does not define ordering or cancellation of multiple confirmations or server persistence.

Common pitfalls

Calling a handler while passing it; expecting a setter to change the local variable; using two replacements to represent two increments; sending messages inside updaters; confusing stopPropagation with preventDefault.

Related topics: Components, props and pure rendering · State structure and component identity · Effects, refs and external synchronization

Take this idea with you

Each handler uses the snapshot of the render that created it. Values passed to a setter request replacement; pure updaters transform pending state. A delayed callback’s correct meaning depends on the instant the contract intends to preserve.

Create account

Reference: Passing event handlers · React 19.3.0; JSX