skip to content

In a nested form model, why must values, errors, touched flags and field registration share one path scheme?

level: middleimportance: must knowfreq 62%

answer

  1. one name, four tables
  2. values, errors, flags, registration
  3. a numeric segment is a position
  4. dots inside a field name collide
  5. unregister when the control goes away

basics

~20 s

A path such as a row position plus a field name is the only name a nested value, its error, its flag and its registered control all agree on. Separate schemes cannot be joined, and a position-based path drifts on reorder.

solid answer

~50 s

A dynamic form's data is nested — objects inside arrays inside objects — so a field is identified by a **path**: segments where a name means an object key and a number means a position in an array. That path is the join key for four tables: the **value**, the **error**, the **interaction flags**, and the **registration** of the control that is currently live. If each table invents its own addressing — errors in a flat list, registration by mount order — nothing can be looked up against anything else, and the form cannot say whether a nested field is invalid or touched. Two things then bite: a path built by joining strings is **ambiguous** when a field name contains the separator, and a numeric segment is a **position**, which moves on reorder, so any table not re-keyed with it drifts.

go deeper

for a junior

Recall that a nested field is named by a path: names for object keys, numbers for array positions. The same path names the field's value, its error and its touched flag.

for a middle

Explain the join: four tables keyed by one path, and what a private scheme costs in each. Be able to show why flattening with a dot is ambiguous and why a numeric segment moves on reorder.

for a senior

Diagnose from symptoms: an error that belongs to the previous row, submit blocked by a field with no control, a rule that stopped running. Tie each back to a table whose key drifted from the value's.

for a principal

Own the scheme as an interface. Decide segment arrays versus flattened keys, how identity relates to position, and whether registration is explicit — every form in the codebase inherits that decision.

## What a path is Once a form holds arrays of objects, a field cannot be named by a single identifier. It is named by a **path** — an ordered list of segments read from the root of the model. A name segment means "key of an object"; a numeric segment means "position in an array". A path is either kept as an array of segments or flattened into a string with a separator, and that choice has consequences (below). The path exists because the form has to answer questions about a field that is *not* a variable anywhere: "what is this field's value", "does it have an error", "has the user touched it", "is its control live right now". Each answer lives in a different structure, and the path is the join key between them. ## The four tables one path has to serve | Table | What it stores | What breaks with a private scheme | |---|---|---| | Values | the data itself, nested | none — this is usually the scheme everything else must adopt | | Errors | a verdict per field | a verdict that cannot be matched to the field that produced it | | Flags | touched, dirty, visited | errors shown before the field was edited, or never shown | | Registration | which controls are live | rules running for fields with no control, or skipped for live ones | The asymmetry matters: values are naturally nested, while errors and flags are usually kept flat, keyed by the stringified path. That is fine — as long as the key really is the same path the value is stored at. ## Ambiguity: the cost of flattening A path flattened with a dot separator is ambiguous the moment a field name contains a dot. A field literally named `a.b` and a nested pair `a` then `b` produce the same key, so one field's error can land on the other, and a lookup can return a sibling's value. The same applies to bracket notation and any name containing a bracket or a quote. The defences: - keep paths as **arrays of segments** internally and flatten only for display or as a map key with an escape rule; - **escape** the separator when flattening, and unescape on parse — never build the key by naive concatenation; - forbid separators in field names at the point the schema is declared, which is cheaper than escaping but constrains the data model; - treat a numeric-looking name segment carefully: if a number always means "array position", an object whose keys are numbers cannot be addressed unambiguously. ## Why a reorder is the hard case A numeric segment encodes a **position**, and position is exactly the property a reorder changes. So after a move: 1. The value array is correct — the entries moved, and the path to each value is *by definition* wherever it now sits. 2. Any flat table keyed by the old stringified path is now wrong: the verdict at position 2 describes whatever used to be there. 3. Registration keyed by path is wrong in the same way, so a rule may run against a control that belongs to a different row. The fix is not to abandon paths — they are how nested data is addressed — but to key the **row** by its own identity and derive the path from the row's current position at the moment of the lookup, or to re-map every path-keyed table inside the same update that performs the move. Either way the move is a single transaction across the tables, not an array operation with cleanup afterwards. ## Registration and the phantom field Registration is the table candidates forget. When a conditional field or a removed row stops rendering, its entry must be withdrawn, or the model still believes a field exists at that path: its rule keeps running, its verdict keeps counting toward overall validity, and submit stays blocked by something with no control to point at. The symmetric bug is over-eager withdrawal — unregistering on any re-render, so a field that is still on screen loses its rule. ## Where models differ A runtime that re-runs the component function tends to rebuild the path for each field on every render, so a path computed once and captured becomes stale. A fine-grained runtime often hands each field a subscription tied to its path, so a moved row needs its subscription re-pointed. A compile-time runtime can resolve some paths statically for fixed fields but not for array positions, which are only known at runtime. The invariant survives all three. ## The practical rules - One scheme, declared once, used by every table. - Paths as segment arrays; flatten only with escaping. - Rows carry identity; positions are derived, never stored as the identity. - Mount and unmount move registration in the same update as the value.

  • Why can a path built by joining names with a dot be ambiguous?
    Because a field name may itself contain the separator. A field literally called `a.b` flattens to the same key as the nested pair `a` then `b`, so a lookup can return the wrong value and an error can land on the wrong field. Keep paths as segment arrays internally and escape the separator when flattening.
  • What goes wrong if a removed row's fields stay registered?
    The model still thinks fields exist at those paths, so their rules keep running and their verdicts keep counting toward validity. Submit is blocked by an error with no control to display it and no way for the user to fix it. Withdrawing registration must happen in the same update as the removal.
  • Should errors be stored nested like values, or flat and keyed by path?
    Either works if the key is the same path. Flat keying makes lookup and clearing by prefix easy, and makes a whole subtree removable with one scan. Nested storage mirrors the values and survives a reorder if the array is moved wholesale, but complicates partial clears. The scheme matters more than the shape.

A path is the form's postal address. The value, the error notice and the delivery receipt all have to be addressed the same way, or one of them ends up at the neighbour's door — and renumbering the street moves every house except the letters already addressed to the old numbers.

saying these in an interview costs you the question

  • Keeps errors in a shape keyed differently from values
  • Assumes reordering the value array carries its errors along
  • Builds flattened paths by concatenation with no escaping
  • Leaves a removed row's fields registered for validation
  • Thinks a touched flag is per-form rather than per-path
  • Captures a computed path once and reuses it after a move