Variant memory
Switching away from a discriminated-union variant doesn't have to mean losing what was typed. Memory snapshots ride alongside the active state and rehydrate on switch-back. Opt out when the variants are unrelated or memory matters.
- Category
- Form option
- Option
useForm({ rememberVariants })- Default
true- Lifetime
- in-memory only (no persistence)
Two side-by-side forms with the same payment schema. Type into the card variant, switch to bank, switch back. The left form (rememberVariants: true) restores your card details; the right form (rememberVariants: false) starts fresh every switch. Same schema, opposite memory policy.
rememberVariants: true(default)
Switch back and the previous variant's typing comes home — memory snapshots ride alongside the active state.
rememberVariants: false(every switch starts fresh)
No memory — every variant switch initialises from the schema's slim default. Type into card, switch to bank, switch back: card's typing is gone.
The default
useForm({
schema,
// rememberVariants: true is Attaform's default
})
Switching back to a previously-visited variant lands on its prior subtree, including nested fields. Each discriminated union at every nesting depth is independently memorized: a top-level union and a nested one each keep their own memory map.
Opting out
useForm({ schema, rememberVariants: false })
With false, every switch drops the outgoing variant's typed state. The new variant initializes from its slim default; the old data is gone.
Use the opt-out when:
- The variants represent unrelated data. A "contact preference" picker between phone and email should clear the phone when switching to email.
- Memory leaks user input you don't want re-applied. A wizard step that should reset when the user backtracks.
- Memory-constrained targets. Snapshots are small per-union, but a deeply nested form with many unions accumulates.
Per-app default
Set the default app-wide via the plugin:
createAttaform({
defaults: { rememberVariants: false },
})
Per-form useForm({ rememberVariants: true }) overrides back to memory-on for forms that want it.
What gets memorized
When the discriminator value flips:
- The current variant's subtree is snapshotted into the memory map keyed by the outgoing variant's discriminator value.
- The new variant's slim default seeds storage.
- On a subsequent switch to a remembered variant, the snapshot rehydrates over the seeded default.
What's stored: the value subtree only. Field-state (touched, blurred, focused, etc.) is NOT part of the snapshot; that travels with the active field state, which lives outside the union's storage tree.
Memory is in-memory only
Variant memory does NOT survive a page reload. Persisted state (useForm({ persist: 'local' })) restores values into form storage on hydration, but the variant memory map starts empty: the first discriminator switch after reload loses any persisted typing in the outgoing variant.
For cross-session continuity of inactive-variant typing, persist beyond the union boundary yourself. Mirror the inactive subtree into a separate persisted slot via a watcher on the discriminator.
reset() and resetField() interactions
reset()clears all variant memory. The reset state becomes the new "no memory" baseline.resetField(path)clears any memory entry whose union path equals or sits underpath. Resetting a single union's path drops only that union's memory; sibling unions retain theirs.
Where to next
- Discriminated unions: the schema feature variant memory rides on top of.
reset&resetField: both interact with the memory map deterministically.- App-wide defaults: set
rememberVariantsonce for every form in the app.