[{"data":1,"prerenderedAt":591},["ShallowReactive",2],{"content-\u002Fdocs\u002Frecipes\u002Fdiscriminated-unions":3},{"id":4,"title":5,"body":6,"description":584,"extension":585,"meta":586,"navigation":61,"path":587,"seo":588,"stem":589,"__hash__":590},"docs\u002Fdocs\u002Frecipes\u002Fdiscriminated-unions.md","Discriminated unions with variant memory",{"type":7,"value":8,"toc":572},"minimark",[9,13,22,27,195,305,315,319,337,343,346,359,367,382,389,478,482,489,496,507,528,534,549,553,568],[10,11,5],"h1",{"id":12},"discriminated-unions-with-variant-memory",[14,15,16,17,21],"p",{},"When a discriminated-union variant changes, attaform reshapes storage\nto the new variant's slim default — the old variant's keys are\npurged, the new variant's keys are seeded. By default, switching\nback to a previously-visited variant restores its prior typed\nsubtree (the \"memory\" — opt-out via ",[18,19,20],"code",{},"rememberVariants: false",").",[23,24,26],"h2",{"id":25},"the-default-behaviour","The default behaviour",[28,29,34],"pre",{"className":30,"code":31,"language":32,"meta":33,"style":33},"language-ts shiki shiki-themes github-light github-dark","import { z } from 'zod'\n\nconst schema = z.object({\n  notify: z.discriminatedUnion('channel', [\n    z.object({ channel: z.literal('email'), address: z.email() }),\n    z.object({ channel: z.literal('sms'), phone: z.string() }),\n  ]),\n})\n\nconst form = useForm({ schema, key: 'notify-prefs' })\n","ts","",[18,35,36,56,63,86,104,132,156,162,168,173],{"__ignoreMap":33},[37,38,41,45,49,52],"span",{"class":39,"line":40},"line",1,[37,42,44],{"class":43},"szBVR","import",[37,46,48],{"class":47},"sVt8B"," { z } ",[37,50,51],{"class":43},"from",[37,53,55],{"class":54},"sZZnC"," 'zod'\n",[37,57,59],{"class":39,"line":58},2,[37,60,62],{"emptyLinePlaceholder":61},true,"\n",[37,64,66,69,73,76,79,83],{"class":39,"line":65},3,[37,67,68],{"class":43},"const",[37,70,72],{"class":71},"sj4cs"," schema",[37,74,75],{"class":43}," =",[37,77,78],{"class":47}," z.",[37,80,82],{"class":81},"sScJk","object",[37,84,85],{"class":47},"({\n",[37,87,89,92,95,98,101],{"class":39,"line":88},4,[37,90,91],{"class":47},"  notify: z.",[37,93,94],{"class":81},"discriminatedUnion",[37,96,97],{"class":47},"(",[37,99,100],{"class":54},"'channel'",[37,102,103],{"class":47},", [\n",[37,105,107,110,112,115,118,120,123,126,129],{"class":39,"line":106},5,[37,108,109],{"class":47},"    z.",[37,111,82],{"class":81},[37,113,114],{"class":47},"({ channel: z.",[37,116,117],{"class":81},"literal",[37,119,97],{"class":47},[37,121,122],{"class":54},"'email'",[37,124,125],{"class":47},"), address: z.",[37,127,128],{"class":81},"email",[37,130,131],{"class":47},"() }),\n",[37,133,135,137,139,141,143,145,148,151,154],{"class":39,"line":134},6,[37,136,109],{"class":47},[37,138,82],{"class":81},[37,140,114],{"class":47},[37,142,117],{"class":81},[37,144,97],{"class":47},[37,146,147],{"class":54},"'sms'",[37,149,150],{"class":47},"), phone: z.",[37,152,153],{"class":81},"string",[37,155,131],{"class":47},[37,157,159],{"class":39,"line":158},7,[37,160,161],{"class":47},"  ]),\n",[37,163,165],{"class":39,"line":164},8,[37,166,167],{"class":47},"})\n",[37,169,171],{"class":39,"line":170},9,[37,172,62],{"emptyLinePlaceholder":61},[37,174,176,178,181,183,186,189,192],{"class":39,"line":175},10,[37,177,68],{"class":43},[37,179,180],{"class":71}," form",[37,182,75],{"class":43},[37,184,185],{"class":81}," useForm",[37,187,188],{"class":47},"({ schema, key: ",[37,190,191],{"class":54},"'notify-prefs'",[37,193,194],{"class":47}," })\n",[28,196,198],{"className":30,"code":197,"language":32,"meta":33,"style":33},"form.setValue('notify.channel', 'email')\nform.setValue('notify.address', 'a@b.com')\n\u002F\u002F storage: { notify: { channel: 'email', address: 'a@b.com' } }\n\nform.setValue('notify.channel', 'sms')\n\u002F\u002F storage: { notify: { channel: 'sms', phone: '' } }\n\u002F\u002F   (email's `address` is purged; sms's `phone` is seeded)\n\nform.setValue('notify.channel', 'email')\n\u002F\u002F storage: { notify: { channel: 'email', address: 'a@b.com' } }\n\u002F\u002F   (`address` restored — variant memory)\n",[18,199,200,221,239,245,249,265,270,275,279,295,299],{"__ignoreMap":33},[37,201,202,205,208,210,213,216,218],{"class":39,"line":40},[37,203,204],{"class":47},"form.",[37,206,207],{"class":81},"setValue",[37,209,97],{"class":47},[37,211,212],{"class":54},"'notify.channel'",[37,214,215],{"class":47},", ",[37,217,122],{"class":54},[37,219,220],{"class":47},")\n",[37,222,223,225,227,229,232,234,237],{"class":39,"line":58},[37,224,204],{"class":47},[37,226,207],{"class":81},[37,228,97],{"class":47},[37,230,231],{"class":54},"'notify.address'",[37,233,215],{"class":47},[37,235,236],{"class":54},"'a@b.com'",[37,238,220],{"class":47},[37,240,241],{"class":39,"line":65},[37,242,244],{"class":243},"sJ8bj","\u002F\u002F storage: { notify: { channel: 'email', address: 'a@b.com' } }\n",[37,246,247],{"class":39,"line":88},[37,248,62],{"emptyLinePlaceholder":61},[37,250,251,253,255,257,259,261,263],{"class":39,"line":106},[37,252,204],{"class":47},[37,254,207],{"class":81},[37,256,97],{"class":47},[37,258,212],{"class":54},[37,260,215],{"class":47},[37,262,147],{"class":54},[37,264,220],{"class":47},[37,266,267],{"class":39,"line":134},[37,268,269],{"class":243},"\u002F\u002F storage: { notify: { channel: 'sms', phone: '' } }\n",[37,271,272],{"class":39,"line":158},[37,273,274],{"class":243},"\u002F\u002F   (email's `address` is purged; sms's `phone` is seeded)\n",[37,276,277],{"class":39,"line":164},[37,278,62],{"emptyLinePlaceholder":61},[37,280,281,283,285,287,289,291,293],{"class":39,"line":170},[37,282,204],{"class":47},[37,284,207],{"class":81},[37,286,97],{"class":47},[37,288,212],{"class":54},[37,290,215],{"class":47},[37,292,122],{"class":54},[37,294,220],{"class":47},[37,296,297],{"class":39,"line":175},[37,298,244],{"class":243},[37,300,302],{"class":39,"line":301},11,[37,303,304],{"class":243},"\u002F\u002F   (`address` restored — variant memory)\n",[14,306,307,310,311,314],{},[18,308,309],{},"rememberVariants"," defaults to ",[18,312,313],{},"true",". Switching back to a\npreviously-visited variant lands on its prior subtree, including\nnested fields. Each discriminated union at every nesting depth is\nindependently memorised.",[23,316,318],{"id":317},"opting-out","Opting out",[28,320,322],{"className":30,"code":321,"language":32,"meta":33,"style":33},"useForm({ schema, rememberVariants: false })\n",[18,323,324],{"__ignoreMap":33},[37,325,326,329,332,335],{"class":39,"line":40},[37,327,328],{"class":81},"useForm",[37,330,331],{"class":47},"({ schema, rememberVariants: ",[37,333,334],{"class":71},"false",[37,336,194],{"class":47},[14,338,339,340,342],{},"With ",[18,341,334],{},", every switch drops the outgoing variant's typed\nstate. The new variant initialises from its slim default; the\nold data is gone.",[14,344,345],{},"Use the opt-out when:",[347,348,349,353,356],"ul",{},[350,351,352],"li",{},"The variants represent unrelated data (a \"type\" picker over\ncontact info should clear the address when switching to phone).",[350,354,355],{},"Memory leaks user input you don't want re-applied (a wizard\nstep that should reset when the user backtracks).",[350,357,358],{},"You're running on memory-constrained targets and the snapshots\nadd up.",[23,360,362,363,366],{"id":361},"caveat-metaerrors-includes-inactive-variant-errors","Caveat: ",[18,364,365],{},"meta.errors"," includes inactive-variant errors",[14,368,369,370,373,374,378,379,381],{},"The form-level ",[18,371,372],{},"form.meta.errors"," aggregate is ",[375,376,377],"strong",{},"unfiltered"," —\nerrors for the inactive variant's fields stay in the array. A\n\"show all\" UI iterating ",[18,380,365],{}," will surface stale errors\nfor the variant the user left.",[14,383,384,385,388],{},"The per-leaf ",[18,386,387],{},"form.errors.\u003Cpath>"," view IS variant-filtered — only\nerrors on the active variant's path show up. Use it for inline\nfield feedback:",[28,390,394],{"className":391,"code":392,"language":393,"meta":33,"style":33},"language-vue shiki shiki-themes github-light github-dark","\u003Cinput v-register=\"form.register('notify.address')\" \u002F>\n\u003Csmall v-if=\"form.errors.notify.address?.[0]\">\n  \u003C!-- only renders when notify.channel === 'email' -->\n  {{ form.errors.notify.address[0].message }}\n\u003C\u002Fsmall>\n","vue",[18,395,396,431,459,464,469],{"__ignoreMap":33},[37,397,398,401,405,408,411,414,416,419,421,423,426,428],{"class":39,"line":40},[37,399,400],{"class":47},"\u003C",[37,402,404],{"class":403},"s9eBZ","input",[37,406,407],{"class":81}," v-register",[37,409,410],{"class":47},"=",[37,412,413],{"class":54},"\"",[37,415,204],{"class":47},[37,417,418],{"class":81},"register",[37,420,97],{"class":47},[37,422,231],{"class":54},[37,424,425],{"class":47},")",[37,427,413],{"class":54},[37,429,430],{"class":47}," \u002F>\n",[37,432,433,435,438,441,443,445,448,451,454,456],{"class":39,"line":58},[37,434,400],{"class":47},[37,436,437],{"class":403},"small",[37,439,440],{"class":43}," v-if",[37,442,410],{"class":47},[37,444,413],{"class":54},[37,446,447],{"class":47},"form.errors.notify.address?.[",[37,449,450],{"class":71},"0",[37,452,453],{"class":47},"]",[37,455,413],{"class":54},[37,457,458],{"class":47},">\n",[37,460,461],{"class":39,"line":65},[37,462,463],{"class":47},"  \u003C!-- only renders when notify.channel === 'email' -->\n",[37,465,466],{"class":39,"line":88},[37,467,468],{"class":47},"  {{ form.errors.notify.address[0].message }}\n",[37,470,471,474,476],{"class":39,"line":106},[37,472,473],{"class":47},"\u003C\u002F",[37,475,437],{"class":403},[37,477,458],{"class":47},[23,479,481],{"id":480},"caveat-memory-is-in-memory-only","Caveat: memory is in-memory only",[14,483,484,485,488],{},"Variant memory does NOT survive a page reload. Persisted state\n(",[18,486,487],{},"useForm({ persist: 'local' })",") restores values into form storage\non hydration, but the variant memory snapshots start empty — the\nfirst discriminator switch after reload loses any persisted typing\nin the outgoing variant.",[14,490,491,492,495],{},"If you need cross-session continuity of inactive-variant typing,\npersist beyond the union boundary yourself (e.g. mirror the\ninactive subtree into a separate persisted slot via\n",[18,493,494],{},"@update:registerValue"," on the discriminator).",[23,497,499,502,503,506],{"id":498},"reset-and-resetfield-interactions",[18,500,501],{},"reset()"," and ",[18,504,505],{},"resetField()"," interactions",[347,508,509,516],{},[350,510,511,515],{},[375,512,513],{},[18,514,501],{}," — clears all variant memory. The reset state\nbecomes the new \"no memory\" baseline.",[350,517,518,523,524,527],{},[375,519,520],{},[18,521,522],{},"resetField(path)"," — clears any memory entry whose union\npath equals or sits under ",[18,525,526],{},"path",". Resetting a single union's\npath drops only that union's memory; sibling unions retain\ntheirs.",[23,529,531,532],{"id":530},"programmatic-switch-via-setvalue","Programmatic switch via ",[18,533,207],{},[14,535,536,537,540,541,544,545,548],{},"The reshape fires for every variant write — ",[18,538,539],{},"setValue('notify', { channel: 'sms', phone: '' })"," reshapes the same way as\n",[18,542,543],{},"setValue('notify.channel', 'sms')",". The structural-completeness\ninvariant kicks in: missing variant keys get filled from the new\nvariant's slim default before the callback's ",[18,546,547],{},"prev"," snapshot.",[23,550,552],{"id":551},"when-the-discriminator-value-itself-is-invalid","When the discriminator value itself is invalid",[14,554,555,556,559,560,563,564,567],{},"If the user types a discriminator value that doesn't match any\nvariant (",[18,557,558],{},"channel: 'fax'"," against ",[18,561,562],{},"'email' | 'sms'","), the reshape\nis skipped — the variant fields stay as they were, and the schema\nsurfaces a refinement error on the discriminator itself. Your\ntemplate's variant-conditional rendering can branch on the\nschema's ",[18,565,566],{},"kind"," rather than relying on the runtime to \"guess\" a\nvariant.",[569,570,571],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":33,"searchDepth":58,"depth":58,"links":573},[574,575,576,578,579,581,583],{"id":25,"depth":58,"text":26},{"id":317,"depth":58,"text":318},{"id":361,"depth":58,"text":577},"Caveat: meta.errors includes inactive-variant errors",{"id":480,"depth":58,"text":481},{"id":498,"depth":58,"text":580},"reset() and resetField() interactions",{"id":530,"depth":58,"text":582},"Programmatic switch via setValue",{"id":551,"depth":58,"text":552},"When a discriminated-union variant changes, attaform reshapes storage\nto the new variant's slim default — the old variant's keys are\npurged, the new variant's keys are seeded. By default, switching\nback to a previously-visited variant restores its prior typed\nsubtree (the \"memory\" — opt-out via rememberVariants: false).","md",{},"\u002Fdocs\u002Frecipes\u002Fdiscriminated-unions",{"title":5,"description":584},"docs\u002Frecipes\u002Fdiscriminated-unions","9u-uMlL4O9THYBSM65upBYLgMLTKWcTSW9A9akjrOVc",1777934136343]