skip to content

How do you decide which source event each field in a shared component library binds on, and what breaks when that policy is inconsistent?

level: principalimportance: should knowfreq 34%

answer

  1. declared default, listed exceptions
  2. which moment is the model true
  3. does anything derive while typing
  4. submit must flush or read controls
  5. write cost differs by reactivity model

basics

~20 s

Pick per field by what depends on the value: per-edit where the interface reacts as the user types, commit where the work is expensive. Declare the rule, because mixed policies leave no single moment when the model is true.

solid answer

~50 s

Decide it as a **declared default plus listed exceptions**, not per component. Bind on the per-edit event when something must react as the user types, or when the value feeds submission; bind on commit when the value is reformatted for display, when the conversion is lossy on half-typed input, or when each write drives expensive work. Then make three things explicit in the component contract: the default source, how a consumer switches it, and who owns the string-to-typed conversion. Leaving it implicit costs you a form with **no single moment** when the model equals the screen — the submit path flushes some fields and not others, and every new field rediscovers the missing-last-edit bug. Write cost is not uniform either: a runtime that re-runs the component function pays a subtree per character where a fine-grained one updates one property, so do not import another stack's rule unmeasured.

go deeper

for a junior

Know that the source event is a choice per field and that the choice decides whether the model is current while someone is still typing in that field.

for a middle

Explain the tradeoff concretely: per-edit enables anything deriving from the value as it is typed, commit keeps the model to deliberate values and suits reformatted display.

for a senior

Make submission safe whatever the policy — flush the focused field or assemble the payload from the live controls — and recognise the cross-field symptoms of a mixed policy.

for a principal

Own the artefact: a default, exceptions with reasons, an override in the component contract, a documented conversion owner, and a cost claim you measured in your own runtime.

## The decision is a policy, not a per-field taste Every bound field answers one question: *at which moment does the model become the truth about this control?* Answer it separately in twenty components and the form as a whole has no answer. So the artefact you actually owe the team is a **policy**: a default source event, an enumerated list of exceptions with reasons, and an explicit way to override. ## What each source buys and costs | Dimension | Per-edit source | Commit source | |---|---|---| | Model freshness | matches the screen continuously | lags while the field has focus | | Per-keystroke features | counters, live filters, as-you-type formatting | impossible without a second listener | | Conversion safety | must tolerate half-typed input | parses only deliberate values | | Display formatting | fights the typist unless write-back is held | applies naturally at commit | | Write cost | one write per character | one write per edit session | | Submit path | reads the model safely | must flush the focused field or read the controls | ## How to choose per field 1. **Does anything on screen derive from the value while typing?** A remaining-characters count, a live result list, a strength meter, a submit button that enables on the first character — if yes, per-edit, and the debate is over. 2. **Is the displayed value reformatted from the model?** Amounts with separators, masked identifiers, normalised casing — prefer commit, or per-edit parsing with the downward write held until focus leaves. 3. **Is the conversion lossy on intermediate input?** Anything where `-` or `1.` is a legal thing to be halfway through typing argues for commit, or for keeping the raw string as the edit buffer. 4. **What does one write cost here?** A write that triggers a large recompute or an outbound request per character is a reason to commit, not a reason to add a timer inside a field component. 5. **Does this field feed submission directly?** Then either bind per-edit or make the submit path responsible for flushing it — and say which, in writing. What is *not* part of this decision: **when a rule judges the value**, which is a separate timing question, and **who owns the value** at all. Conflating the three is how a field component ends up with a single option that secretly changes all three behaviours. ## What inconsistency actually costs - **No single truthful moment.** Some fields are current and some are one edit behind, and which is which depends on where focus happens to be. Every reader of the model inherits that uncertainty. - **A submit path full of special cases.** Flush this field, read that control, trust the third — and the rule is carried in someone's head rather than in the code. - **The same bug, re-fixed.** The last-edit-missing defect gets a local patch per form instead of one policy change. - **Cross-field derived values that disagree.** Two inputs of one computed total commit at different moments, so the total is briefly wrong in a way that is reproducible but never quite the same twice. - **Test and automation cost.** Anything driving a field has to know which event that field listens to, so a helper that works for one component silently does nothing for another. (How a test drives a binding belongs to the testing topic; the point here is that an inconsistent policy is what makes it hard.) - **Analytics and undo granularity** differ per field for no product reason. ## Making submission safe regardless of the policy Whatever the policy, submission should not depend on remembering it: 1. **Flush before read** — commit the focused field explicitly as the first step of the submit handler. 2. **Or read the controls** — assemble the payload from the live form (`FormData` over the form element) so the model's lag cannot matter, accepting that coercion moves to that layer. 3. **Or require per-edit binding** for every field whose value is submitted, and make the exceptions the fields that are *not* submitted. All three are defensible; what is not defensible is having no stated answer, because then each form gets whichever one its author remembered. ## Cost is model-dependent, so do not import someone else's rule In a runtime that **re-runs the component function** on each state write, a per-keystroke binding re-runs that function and reconciles its subtree per character, so teams there develop strong habits about commit sources and memoisation. In a runtime with **fine-grained tracking**, the same keystroke updates the one dependency that reads the value, and the habit looks like superstition. A **compile-time** runtime generates direct updates and lands nearer the second. Take the reasoning, not the rule: measure a realistic field count on a realistic device before declaring per-keystroke binding too expensive. ## What to say when asked Lead with the policy framing: a declared default, exceptions with reasons, an explicit override, and a documented owner for conversion. Then name the failure the policy prevents — a form with no single moment when the model is true — and the submission guarantee that makes the choice safe either way. That is the difference between an opinion about keystrokes and a contract a team can hold.

  • What belongs in the field component's public contract for this?
    The default source event, an explicit way to switch it, whether the component parses to a typed value or emits the raw string, and whether it ever writes the control outside a model change. Those four make a field composable; leaving any implicit forces consumers to read the implementation.
  • A team proposes per-edit binding everywhere so the model is always current. What is the honest counter-argument?
    Formatted and lossily-converted fields fight the typist under a per-edit source unless the downward write is held, and in a re-run-the-function runtime each keystroke re-renders a subtree. Per-edit everywhere is a good default, not a free one: pair it with an edit buffer and a held write-back, or accept exceptions.
  • Why is a timer inside a field component a poor substitute for choosing a source event?
    It adds a third moment nobody declared — neither edit nor commit — so the model is now true at an unpredictable delay that varies with typing speed. It also hides the cost question rather than answering it, and every consumer inherits a latency they cannot see in the contract.

saying these in an interview costs you the question

  • Decides the source event per component with no stated policy
  • Assumes the model is always current regardless of the binding source
  • Bundles source event, conversion and value ownership into one option
  • Imports a per-keystroke cost rule from a different reactivity model unmeasured
  • Leaves the submit path to remember which fields need flushing
  • Adds a timer inside the field instead of choosing a moment