Skip to content

refactor: use Atom.debounce for validation and auto-submit debouncing#100

Merged
lucas-barake merged 2 commits into
mainfrom
refactor/atom-debounce
Jul 10, 2026
Merged

refactor: use Atom.debounce for validation and auto-submit debouncing#100
lucas-barake merged 2 commits into
mainfrom
refactor/atom-debounce

Conversation

@lucas-barake

Copy link
Copy Markdown
Owner

What

Replaces the two hand-rolled setTimeout/clearTimeout debounces in FormAtoms.ts with compositions over effect v4's Atom.debounce, and keeps the debounce duration as Duration.Input end-to-end in ParsedMode instead of eagerly collapsing it to millis in Mode.parse (Atom.debounce accepts Duration.Input directly).

How

triggerValidationAtom: when debouncing applies (onChange + positive debounce + not autoSubmit), value changes flow through Atom.debounce over a boxed { value } wrapper of valueAtom, and the subscriber evaluates shouldValidateAtom when the debounced value lands before firing the validation. The box matters: Atom.debounce drops updates that are Object.is-equal to its current value, so debouncing valueAtom directly would swallow a change that reverts to the pre-burst value within the window — the wrapper produces a fresh reference per change, preserving the previous behavior of validating in that case. The un-debounced path (zero/absent debounce, onBlur, onSubmit gating) and the immediate blur-triggered validation are unchanged.

autoSubmitAtom: submit requests are funneled through a monotonically increasing counter atom debounced with Atom.debounce; every bump restarts the trailing window and the subscriber submits when it lands. The in-flight re-entrancy queue is intentionally kept as a subscription to submitAtom completion (it is not a debounce concern): a change during an in-flight submit marks pendingChanges and yields exactly one debounced follow-up submit after completion, and reference-equal value updates still never submit. Zero/absent debounce still submits synchronously.

Atom.debounce is built on Atom.transform, which already applies the setIdleTTL(0) convention this file uses, and its finalizer clears any pending timer on unmount — matching the manual cleanup that was removed.

Semantics

No intentional changes. One equivalence worth noting: the old code checked shouldValidateAtom when scheduling the timer, the new code checks it when the debounced value lands. Debouncing only ever applies in onChange mode, where shouldValidateAtom is constantly true, so the two are indistinguishable — and gate-at-fire-time is the more correct reading.

Tests

Nine regression tests were added first and verified green against the previous implementation, then kept green through the refactor:

  • exactly one validation after the quiet period for rapid changes (none before it)
  • validation still fires when the value reverts to its pre-burst value within the window
  • immediate validation when debounce is 0
  • immediate validation when debounce is absent
  • validation is not debounced when autoSubmit is enabled
  • no validation on change before touch in onBlur mode (shouldValidate gate)
  • immediate (un-debounced) validation on blur in onBlur mode
  • exactly one auto-submit after the quiet period for rapid changes
  • exactly one debounced follow-up submit for changes made while a submit is in flight

Mode.test.ts expectations updated for the Duration.Input passthrough. Full suite: 310 tests passing, check:types and lint clean.

Replace the two hand-rolled setTimeout/clearTimeout debounces in FormAtoms
with compositions over Atom.debounce, and keep the debounce duration as
Duration.Input end-to-end in ParsedMode instead of eagerly collapsing to
millis (Atom.debounce accepts Duration.Input directly).

Preserved semantics:
- Validation debounces only for onChange + positive debounce + not
  autoSubmit; zero/absent debounce still validates synchronously.
- shouldValidate is evaluated when the debounced value lands, and the
  onBlur blur-triggered validation stays immediate (un-debounced).
- Value changes are debounced through a boxed { value } wrapper so a
  change that reverts to the pre-burst value within the window still
  triggers validation (Atom.debounce alone would drop it via Object.is).
- Auto-submit funnels submit requests through a counter atom debounced
  with Atom.debounce; the in-flight pendingChanges queue is kept via the
  existing submitAtom subscription, so a change during an in-flight
  submit still yields exactly one debounced follow-up submit, and
  reference-equal value updates still never submit.

Regression tests added first and verified against the previous
implementation: exact-count debounced validation for rapid changes,
revert-within-window validation, immediate validation for zero/absent
debounce and for autoSubmit mode, onBlur gating before touch, immediate
blur validation, exact-count debounced auto-submit, and the in-flight
follow-up submit queue.
@lucas-barake
lucas-barake force-pushed the refactor/atom-debounce branch from e84a690 to a20d230 Compare July 10, 2026 02:41
@lucas-barake
lucas-barake merged commit 5e30412 into main Jul 10, 2026
4 checks passed
@lucas-barake
lucas-barake deleted the refactor/atom-debounce branch July 10, 2026 02:41
@github-actions github-actions Bot mentioned this pull request Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant