[{"data":1,"prerenderedAt":1473},["ShallowReactive",2],{"content-\u002Fdocs\u002Frecipes\u002Fdynamic-field-arrays":3},{"id":4,"title":5,"body":6,"description":16,"extension":1467,"meta":1468,"navigation":70,"path":1469,"seo":1470,"stem":1471,"__hash__":1472},"docs\u002Fdocs\u002Frecipes\u002Fdynamic-field-arrays.md","Dynamic field arrays",{"type":7,"value":8,"toc":1458},"minimark",[9,13,17,22,383,386,411,414,441,445,779,786,790,796,807,860,910,920,991,994,997,1001,1065,1167,1178,1211,1226,1230,1236,1269,1276,1280,1286,1309,1314,1318,1340,1437,1454],[10,11,5],"h1",{"id":12},"dynamic-field-arrays",[14,15,16],"p",{},"Forms that edit a list — tags on a post, line items on an invoice,\nsocial links on a profile — get seven typed helpers on any array\npath.",[18,19,21],"h2",{"id":20},"the-helpers","The helpers",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-ts shiki shiki-themes github-light github-dark","import { z } from 'zod'\nimport { useForm } from 'attaform\u002Fzod'\n\nconst schema = z.object({\n  tags: z.array(z.string()),\n  posts: z.array(\n    z.object({\n      title: z.string(),\n      views: z.number(),\n    })\n  ),\n})\n\nconst form = useForm({ schema, key: 'blog-editor' })\n\nform.append('tags', 'new-tag') \u002F\u002F push\nform.prepend('tags', 'first-tag') \u002F\u002F unshift\nform.insert('tags', 2, 'at-index-two') \u002F\u002F splice-insert\nform.remove('tags', 0) \u002F\u002F splice-remove\nform.swap('tags', 0, 2) \u002F\u002F exchange two indices\nform.move('tags', 3, 1) \u002F\u002F move from → to, shift others\nform.replace('tags', 0, 'replaced-in-place') \u002F\u002F in-place; never grows\n","ts","",[30,31,32,52,65,72,95,113,124,134,145,156,162,168,174,179,201,206,234,256,283,305,330,357],"code",{"__ignoreMap":28},[33,34,37,41,45,48],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"szBVR","import",[33,42,44],{"class":43},"sVt8B"," { z } ",[33,46,47],{"class":39},"from",[33,49,51],{"class":50},"sZZnC"," 'zod'\n",[33,53,55,57,60,62],{"class":35,"line":54},2,[33,56,40],{"class":39},[33,58,59],{"class":43}," { useForm } ",[33,61,47],{"class":39},[33,63,64],{"class":50}," 'attaform\u002Fzod'\n",[33,66,68],{"class":35,"line":67},3,[33,69,71],{"emptyLinePlaceholder":70},true,"\n",[33,73,75,78,82,85,88,92],{"class":35,"line":74},4,[33,76,77],{"class":39},"const",[33,79,81],{"class":80},"sj4cs"," schema",[33,83,84],{"class":39}," =",[33,86,87],{"class":43}," z.",[33,89,91],{"class":90},"sScJk","object",[33,93,94],{"class":43},"({\n",[33,96,98,101,104,107,110],{"class":35,"line":97},5,[33,99,100],{"class":43},"  tags: z.",[33,102,103],{"class":90},"array",[33,105,106],{"class":43},"(z.",[33,108,109],{"class":90},"string",[33,111,112],{"class":43},"()),\n",[33,114,116,119,121],{"class":35,"line":115},6,[33,117,118],{"class":43},"  posts: z.",[33,120,103],{"class":90},[33,122,123],{"class":43},"(\n",[33,125,127,130,132],{"class":35,"line":126},7,[33,128,129],{"class":43},"    z.",[33,131,91],{"class":90},[33,133,94],{"class":43},[33,135,137,140,142],{"class":35,"line":136},8,[33,138,139],{"class":43},"      title: z.",[33,141,109],{"class":90},[33,143,144],{"class":43},"(),\n",[33,146,148,151,154],{"class":35,"line":147},9,[33,149,150],{"class":43},"      views: z.",[33,152,153],{"class":90},"number",[33,155,144],{"class":43},[33,157,159],{"class":35,"line":158},10,[33,160,161],{"class":43},"    })\n",[33,163,165],{"class":35,"line":164},11,[33,166,167],{"class":43},"  ),\n",[33,169,171],{"class":35,"line":170},12,[33,172,173],{"class":43},"})\n",[33,175,177],{"class":35,"line":176},13,[33,178,71],{"emptyLinePlaceholder":70},[33,180,182,184,187,189,192,195,198],{"class":35,"line":181},14,[33,183,77],{"class":39},[33,185,186],{"class":80}," form",[33,188,84],{"class":39},[33,190,191],{"class":90}," useForm",[33,193,194],{"class":43},"({ schema, key: ",[33,196,197],{"class":50},"'blog-editor'",[33,199,200],{"class":43}," })\n",[33,202,204],{"class":35,"line":203},15,[33,205,71],{"emptyLinePlaceholder":70},[33,207,209,212,215,218,221,224,227,230],{"class":35,"line":208},16,[33,210,211],{"class":43},"form.",[33,213,214],{"class":90},"append",[33,216,217],{"class":43},"(",[33,219,220],{"class":50},"'tags'",[33,222,223],{"class":43},", ",[33,225,226],{"class":50},"'new-tag'",[33,228,229],{"class":43},") ",[33,231,233],{"class":232},"sJ8bj","\u002F\u002F push\n",[33,235,237,239,242,244,246,248,251,253],{"class":35,"line":236},17,[33,238,211],{"class":43},[33,240,241],{"class":90},"prepend",[33,243,217],{"class":43},[33,245,220],{"class":50},[33,247,223],{"class":43},[33,249,250],{"class":50},"'first-tag'",[33,252,229],{"class":43},[33,254,255],{"class":232},"\u002F\u002F unshift\n",[33,257,259,261,264,266,268,270,273,275,278,280],{"class":35,"line":258},18,[33,260,211],{"class":43},[33,262,263],{"class":90},"insert",[33,265,217],{"class":43},[33,267,220],{"class":50},[33,269,223],{"class":43},[33,271,272],{"class":80},"2",[33,274,223],{"class":43},[33,276,277],{"class":50},"'at-index-two'",[33,279,229],{"class":43},[33,281,282],{"class":232},"\u002F\u002F splice-insert\n",[33,284,286,288,291,293,295,297,300,302],{"class":35,"line":285},19,[33,287,211],{"class":43},[33,289,290],{"class":90},"remove",[33,292,217],{"class":43},[33,294,220],{"class":50},[33,296,223],{"class":43},[33,298,299],{"class":80},"0",[33,301,229],{"class":43},[33,303,304],{"class":232},"\u002F\u002F splice-remove\n",[33,306,308,310,313,315,317,319,321,323,325,327],{"class":35,"line":307},20,[33,309,211],{"class":43},[33,311,312],{"class":90},"swap",[33,314,217],{"class":43},[33,316,220],{"class":50},[33,318,223],{"class":43},[33,320,299],{"class":80},[33,322,223],{"class":43},[33,324,272],{"class":80},[33,326,229],{"class":43},[33,328,329],{"class":232},"\u002F\u002F exchange two indices\n",[33,331,333,335,338,340,342,344,347,349,352,354],{"class":35,"line":332},21,[33,334,211],{"class":43},[33,336,337],{"class":90},"move",[33,339,217],{"class":43},[33,341,220],{"class":50},[33,343,223],{"class":43},[33,345,346],{"class":80},"3",[33,348,223],{"class":43},[33,350,351],{"class":80},"1",[33,353,229],{"class":43},[33,355,356],{"class":232},"\u002F\u002F move from → to, shift others\n",[33,358,360,362,365,367,369,371,373,375,378,380],{"class":35,"line":359},22,[33,361,211],{"class":43},[33,363,364],{"class":90},"replace",[33,366,217],{"class":43},[33,368,220],{"class":50},[33,370,223],{"class":43},[33,372,299],{"class":80},[33,374,223],{"class":43},[33,376,377],{"class":50},"'replaced-in-place'",[33,379,229],{"class":43},[33,381,382],{"class":232},"\u002F\u002F in-place; never grows\n",[14,384,385],{},"Every helper is type-narrowed:",[387,388,389,401],"ul",{},[390,391,392,393,396,397,400],"li",{},"Path is ",[30,394,395],{},"ArrayPath\u003CForm>"," — ",[30,398,399],{},"append('title', …)"," on a string field is a compile error.",[390,402,403,404,396,407,410],{},"Value is ",[30,405,406],{},"ArrayItem\u003CForm, Path>",[30,408,409],{},"append('posts', 'not-a-post')"," is a compile error.",[14,412,413],{},"Out-of-range behaviour:",[387,415,416,428],{},[390,417,418,420,421,420,423,420,425,427],{},[30,419,290],{}," \u002F ",[30,422,312],{},[30,424,337],{},[30,426,364],{}," no-op on invalid indices.",[390,429,430,432,433,436,437,440],{},[30,431,263],{}," clamps via ",[30,434,435],{},"Array.prototype.splice"," semantics\n(",[30,438,439],{},"splice(-1, 0, v)"," inserts just before the last item).",[18,442,444],{"id":443},"the-v-for-pattern","The v-for pattern",[23,446,450],{"className":447,"code":448,"language":449,"meta":28,"style":28},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\n  import { useForm } from 'attaform\u002Fzod'\n  import { z } from 'zod'\n\n  const schema = z.object({\n    posts: z.array(\n      z.object({\n        title: z.string(),\n        views: z.number(),\n      })\n    ),\n  })\n\n  const form = useForm({ schema, key: 'blog-editor' })\n  const posts = computed(() => form.values.posts ?? [])\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-for=\"(post, index) in posts\" :key=\"index\">\n    \u003Cinput v-register=\"form.register(`posts.${index}.title`)\" \u002F>\n    \u003Cinput v-register=\"form.register(`posts.${index}.views`)\" type=\"number\" \u002F>\n    \u003Cbutton type=\"button\" @click=\"form.remove('posts', index)\">Remove\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cbutton type=\"button\" @click=\"form.append('posts', { title: '', views: 0 })\"> Add post \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n","vue",[30,451,452,476,487,497,501,516,525,534,543,552,557,562,567,571,587,614,623,627,636,662,681,704,733,743,770],{"__ignoreMap":28},[33,453,454,457,461,464,467,470,473],{"class":35,"line":36},[33,455,456],{"class":43},"\u003C",[33,458,460],{"class":459},"s9eBZ","script",[33,462,463],{"class":90}," setup",[33,465,466],{"class":90}," lang",[33,468,469],{"class":43},"=",[33,471,472],{"class":50},"\"ts\"",[33,474,475],{"class":43},">\n",[33,477,478,481,483,485],{"class":35,"line":54},[33,479,480],{"class":39},"  import",[33,482,59],{"class":43},[33,484,47],{"class":39},[33,486,64],{"class":50},[33,488,489,491,493,495],{"class":35,"line":67},[33,490,480],{"class":39},[33,492,44],{"class":43},[33,494,47],{"class":39},[33,496,51],{"class":50},[33,498,499],{"class":35,"line":74},[33,500,71],{"emptyLinePlaceholder":70},[33,502,503,506,508,510,512,514],{"class":35,"line":97},[33,504,505],{"class":39},"  const",[33,507,81],{"class":80},[33,509,84],{"class":39},[33,511,87],{"class":43},[33,513,91],{"class":90},[33,515,94],{"class":43},[33,517,518,521,523],{"class":35,"line":115},[33,519,520],{"class":43},"    posts: z.",[33,522,103],{"class":90},[33,524,123],{"class":43},[33,526,527,530,532],{"class":35,"line":126},[33,528,529],{"class":43},"      z.",[33,531,91],{"class":90},[33,533,94],{"class":43},[33,535,536,539,541],{"class":35,"line":136},[33,537,538],{"class":43},"        title: z.",[33,540,109],{"class":90},[33,542,144],{"class":43},[33,544,545,548,550],{"class":35,"line":147},[33,546,547],{"class":43},"        views: z.",[33,549,153],{"class":90},[33,551,144],{"class":43},[33,553,554],{"class":35,"line":158},[33,555,556],{"class":43},"      })\n",[33,558,559],{"class":35,"line":164},[33,560,561],{"class":43},"    ),\n",[33,563,564],{"class":35,"line":170},[33,565,566],{"class":43},"  })\n",[33,568,569],{"class":35,"line":176},[33,570,71],{"emptyLinePlaceholder":70},[33,572,573,575,577,579,581,583,585],{"class":35,"line":181},[33,574,505],{"class":39},[33,576,186],{"class":80},[33,578,84],{"class":39},[33,580,191],{"class":90},[33,582,194],{"class":43},[33,584,197],{"class":50},[33,586,200],{"class":43},[33,588,589,591,594,596,599,602,605,608,611],{"class":35,"line":203},[33,590,505],{"class":39},[33,592,593],{"class":80}," posts",[33,595,84],{"class":39},[33,597,598],{"class":90}," computed",[33,600,601],{"class":43},"(() ",[33,603,604],{"class":39},"=>",[33,606,607],{"class":43}," form.values.posts ",[33,609,610],{"class":39},"??",[33,612,613],{"class":43}," [])\n",[33,615,616,619,621],{"class":35,"line":208},[33,617,618],{"class":43},"\u003C\u002F",[33,620,460],{"class":459},[33,622,475],{"class":43},[33,624,625],{"class":35,"line":236},[33,626,71],{"emptyLinePlaceholder":70},[33,628,629,631,634],{"class":35,"line":258},[33,630,456],{"class":43},[33,632,633],{"class":459},"template",[33,635,475],{"class":43},[33,637,638,641,644,647,649,652,655,657,660],{"class":35,"line":285},[33,639,640],{"class":43},"  \u003C",[33,642,643],{"class":459},"div",[33,645,646],{"class":90}," v-for",[33,648,469],{"class":43},[33,650,651],{"class":50},"\"(post, index) in posts\"",[33,653,654],{"class":90}," :key",[33,656,469],{"class":43},[33,658,659],{"class":50},"\"index\"",[33,661,475],{"class":43},[33,663,664,667,670,673,675,678],{"class":35,"line":307},[33,665,666],{"class":43},"    \u003C",[33,668,669],{"class":459},"input",[33,671,672],{"class":90}," v-register",[33,674,469],{"class":43},[33,676,677],{"class":50},"\"form.register(`posts.${index}.title`)\"",[33,679,680],{"class":43}," \u002F>\n",[33,682,683,685,687,689,691,694,697,699,702],{"class":35,"line":332},[33,684,666],{"class":43},[33,686,669],{"class":459},[33,688,672],{"class":90},[33,690,469],{"class":43},[33,692,693],{"class":50},"\"form.register(`posts.${index}.views`)\"",[33,695,696],{"class":90}," type",[33,698,469],{"class":43},[33,700,701],{"class":50},"\"number\"",[33,703,680],{"class":43},[33,705,706,708,711,713,715,718,721,723,726,729,731],{"class":35,"line":359},[33,707,666],{"class":43},[33,709,710],{"class":459},"button",[33,712,696],{"class":90},[33,714,469],{"class":43},[33,716,717],{"class":50},"\"button\"",[33,719,720],{"class":90}," @click",[33,722,469],{"class":43},[33,724,725],{"class":50},"\"form.remove('posts', index)\"",[33,727,728],{"class":43},">Remove\u003C\u002F",[33,730,710],{"class":459},[33,732,475],{"class":43},[33,734,736,739,741],{"class":35,"line":735},23,[33,737,738],{"class":43},"  \u003C\u002F",[33,740,643],{"class":459},[33,742,475],{"class":43},[33,744,746,748,750,752,754,756,758,760,763,766,768],{"class":35,"line":745},24,[33,747,640],{"class":43},[33,749,710],{"class":459},[33,751,696],{"class":90},[33,753,469],{"class":43},[33,755,717],{"class":50},[33,757,720],{"class":90},[33,759,469],{"class":43},[33,761,762],{"class":50},"\"form.append('posts', { title: '', views: 0 })\"",[33,764,765],{"class":43},"> Add post \u003C\u002F",[33,767,710],{"class":459},[33,769,475],{"class":43},[33,771,773,775,777],{"class":35,"line":772},25,[33,774,618],{"class":43},[33,776,633],{"class":459},[33,778,475],{"class":43},[14,780,781,782,785],{},"Template literals like ",[30,783,784],{},"`posts.${index}.title`"," type-narrow\ncorrectly — you get the same type safety as a static path.",[18,787,789],{"id":788},"keying-rows-when-items-dont-have-ids","Keying rows when items don't have IDs",[14,791,792,795],{},[30,793,794],{},":key=\"index\""," is fine for a display-only list but breaks when rows\nreorder or get removed (Vue reuses nodes across what are\nconceptually different rows). Two patterns that work:",[14,797,798,802,803,806],{},[799,800,801],"strong",{},"1. Client-generated stable ID on append."," Add an ",[30,804,805],{},"id"," to the\nrow and key by it:",[23,808,810],{"className":25,"code":809,"language":27,"meta":28,"style":28},"form.append('posts', {\n  id: crypto.randomUUID(),\n  title: '',\n  views: 0,\n})\n",[30,811,812,826,836,847,856],{"__ignoreMap":28},[33,813,814,816,818,820,823],{"class":35,"line":36},[33,815,211],{"class":43},[33,817,214],{"class":90},[33,819,217],{"class":43},[33,821,822],{"class":50},"'posts'",[33,824,825],{"class":43},", {\n",[33,827,828,831,834],{"class":35,"line":54},[33,829,830],{"class":43},"  id: crypto.",[33,832,833],{"class":90},"randomUUID",[33,835,144],{"class":43},[33,837,838,841,844],{"class":35,"line":67},[33,839,840],{"class":43},"  title: ",[33,842,843],{"class":50},"''",[33,845,846],{"class":43},",\n",[33,848,849,852,854],{"class":35,"line":74},[33,850,851],{"class":43},"  views: ",[33,853,299],{"class":80},[33,855,846],{"class":43},[33,857,858],{"class":35,"line":97},[33,859,173],{"class":43},[23,861,863],{"className":447,"code":862,"language":449,"meta":28,"style":28},"\u003Cdiv v-for=\"post in posts\" :key=\"post.id\">…\u003C\u002Fdiv>\n",[30,864,865],{"__ignoreMap":28},[33,866,867,869,871,873,875,878,881,884,886,888,891,894,896,898,901,903,906,908],{"class":35,"line":36},[33,868,456],{"class":43},[33,870,643],{"class":459},[33,872,646],{"class":39},[33,874,469],{"class":43},[33,876,877],{"class":50},"\"",[33,879,880],{"class":43},"post ",[33,882,883],{"class":39},"in",[33,885,593],{"class":43},[33,887,877],{"class":50},[33,889,890],{"class":43}," :",[33,892,893],{"class":90},"key",[33,895,469],{"class":43},[33,897,877],{"class":50},[33,899,900],{"class":43},"post.id",[33,902,877],{"class":50},[33,904,905],{"class":43},">…\u003C\u002F",[33,907,643],{"class":459},[33,909,475],{"class":43},[14,911,912,915,916,919],{},[799,913,914],{},"2. External counter."," ",[30,917,918],{},"nextId"," ticks per append:",[23,921,923],{"className":25,"code":922,"language":27,"meta":28,"style":28},"const nextId = ref(0)\nfunction addPost() {\n  form.append('posts', { title: '', views: 0 })\n  nextId.value++\n}\n",[30,924,925,944,955,978,986],{"__ignoreMap":28},[33,926,927,929,932,934,937,939,941],{"class":35,"line":36},[33,928,77],{"class":39},[33,930,931],{"class":80}," nextId",[33,933,84],{"class":39},[33,935,936],{"class":90}," ref",[33,938,217],{"class":43},[33,940,299],{"class":80},[33,942,943],{"class":43},")\n",[33,945,946,949,952],{"class":35,"line":54},[33,947,948],{"class":39},"function",[33,950,951],{"class":90}," addPost",[33,953,954],{"class":43},"() {\n",[33,956,957,960,962,964,966,969,971,974,976],{"class":35,"line":67},[33,958,959],{"class":43},"  form.",[33,961,214],{"class":90},[33,963,217],{"class":43},[33,965,822],{"class":50},[33,967,968],{"class":43},", { title: ",[33,970,843],{"class":50},[33,972,973],{"class":43},", views: ",[33,975,299],{"class":80},[33,977,200],{"class":43},[33,979,980,983],{"class":35,"line":74},[33,981,982],{"class":43},"  nextId.value",[33,984,985],{"class":39},"++\n",[33,987,988],{"class":35,"line":97},[33,989,990],{"class":43},"}\n",[14,992,993],{},"Weaker (remounts reset the counter) but good enough in-session.",[14,995,996],{},"For lists that only ever append and never reorder, raw index keys\nare OK.",[18,998,1000],{"id":999},"values-vs-fields","values vs fields",[387,1002,1003,1025],{},[390,1004,1005,1008,1009,1012,1013,1016,1017,1020,1021,1024],{},[30,1006,1007],{},"form.values.posts[0]?.title"," → ",[30,1010,1011],{},"string | undefined",". Reads carry\n",[30,1014,1015],{},"| undefined"," once a path crosses an array index — at runtime,\n",[30,1018,1019],{},"posts[0]"," could be missing (sparse, deleted, fresh-mount empty).\nNarrow with ",[30,1022,1023],{},"?."," \u002F optional checks before non-null operations.\nTuple positions stay strict (their length is static).",[390,1026,1027,1030,1031,223,1034,223,1037,223,1040,223,1043,846,1046,223,1049,223,1052,223,1055,1058,1059,1061,1062,1064],{},[30,1028,1029],{},"form.fields.posts[0].title"," → reactive per-field state at the\npath. Leaf props: ",[30,1032,1033],{},"value",[30,1035,1036],{},"dirty",[30,1038,1039],{},"errors",[30,1041,1042],{},"touched",[30,1044,1045],{},"focused",[30,1047,1048],{},"blurred",[30,1050,1051],{},"blank",[30,1053,1054],{},"isConnected",[30,1056,1057],{},"path",". Same ",[30,1060,1015],{},"\ntaint on ",[30,1063,1033],{}," once an array index is crossed.",[23,1066,1068],{"className":447,"code":1067,"language":449,"meta":28,"style":28},"\u003Ctemplate>\n  \u003Cdiv v-for=\"(post, index) in posts\" :key=\"post.id\">\n    \u003Cinput v-register=\"form.register(`posts.${index}.title`)\" \u002F>\n    \u003Cspan v-if=\"form.fields.posts[index].title.errors.length > 0\" class=\"error\">\n      {{ form.fields.posts[index].title.errors[0].message }}\n    \u003C\u002Fspan>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[30,1069,1070,1078,1099,1113,1137,1142,1151,1159],{"__ignoreMap":28},[33,1071,1072,1074,1076],{"class":35,"line":36},[33,1073,456],{"class":43},[33,1075,633],{"class":459},[33,1077,475],{"class":43},[33,1079,1080,1082,1084,1086,1088,1090,1092,1094,1097],{"class":35,"line":54},[33,1081,640],{"class":43},[33,1083,643],{"class":459},[33,1085,646],{"class":90},[33,1087,469],{"class":43},[33,1089,651],{"class":50},[33,1091,654],{"class":90},[33,1093,469],{"class":43},[33,1095,1096],{"class":50},"\"post.id\"",[33,1098,475],{"class":43},[33,1100,1101,1103,1105,1107,1109,1111],{"class":35,"line":67},[33,1102,666],{"class":43},[33,1104,669],{"class":459},[33,1106,672],{"class":90},[33,1108,469],{"class":43},[33,1110,677],{"class":50},[33,1112,680],{"class":43},[33,1114,1115,1117,1119,1122,1124,1127,1130,1132,1135],{"class":35,"line":74},[33,1116,666],{"class":43},[33,1118,33],{"class":459},[33,1120,1121],{"class":90}," v-if",[33,1123,469],{"class":43},[33,1125,1126],{"class":50},"\"form.fields.posts[index].title.errors.length > 0\"",[33,1128,1129],{"class":90}," class",[33,1131,469],{"class":43},[33,1133,1134],{"class":50},"\"error\"",[33,1136,475],{"class":43},[33,1138,1139],{"class":35,"line":97},[33,1140,1141],{"class":43},"      {{ form.fields.posts[index].title.errors[0].message }}\n",[33,1143,1144,1147,1149],{"class":35,"line":115},[33,1145,1146],{"class":43},"    \u003C\u002F",[33,1148,33],{"class":459},[33,1150,475],{"class":43},[33,1152,1153,1155,1157],{"class":35,"line":126},[33,1154,738],{"class":43},[33,1156,643],{"class":459},[33,1158,475],{"class":43},[33,1160,1161,1163,1165],{"class":35,"line":136},[33,1162,618],{"class":43},[33,1164,633],{"class":459},[33,1166,475],{"class":43},[14,1168,1169,1170,1173,1174,1177],{},"The directive's ",[30,1171,1172],{},"v-register"," binding handles ",[30,1175,1176],{},"undefined"," correctly\n(renders empty), so most templates don't need defensive narrowing.\nDefensive narrowing matters when scripts read the value:",[23,1179,1181],{"className":25,"code":1180,"language":27,"meta":28,"style":28},"const upper = form.values.posts[0]?.title?.toUpperCase() ?? ''\n",[30,1182,1183],{"__ignoreMap":28},[33,1184,1185,1187,1190,1192,1195,1197,1200,1203,1206,1208],{"class":35,"line":36},[33,1186,77],{"class":39},[33,1188,1189],{"class":80}," upper",[33,1191,84],{"class":39},[33,1193,1194],{"class":43}," form.values.posts[",[33,1196,299],{"class":80},[33,1198,1199],{"class":43},"]?.title?.",[33,1201,1202],{"class":90},"toUpperCase",[33,1204,1205],{"class":43},"() ",[33,1207,610],{"class":39},[33,1209,1210],{"class":50}," ''\n",[14,1212,1213,1214,1217,1218,1221,1222,1225],{},"For ref-shaped interop (e.g. ",[30,1215,1216],{},"watch(emailRef, …)"," \u002F external\ncomposables), use ",[30,1219,1220],{},"form.toRef('posts.0.title')"," to get a\n",[30,1223,1224],{},"Readonly\u003CRef\u003Cstring | undefined>>"," at the same path.",[18,1227,1229],{"id":1228},"stale-state-on-removal","Stale state on removal",[14,1231,1232,1233,1235],{},"When you ",[30,1234,290],{}," a row, errors + field records at the removed\npath stay in the store until explicitly cleared — the helper can't\nknow which indices map to which errors after a shift. If the stale\nstate matters to you:",[23,1237,1239],{"className":25,"code":1238,"language":27,"meta":28,"style":28},"form.remove('posts', 2)\nform.clearFieldErrors() \u002F\u002F or form.resetField('posts') for full clean slate\n",[30,1240,1241,1257],{"__ignoreMap":28},[33,1242,1243,1245,1247,1249,1251,1253,1255],{"class":35,"line":36},[33,1244,211],{"class":43},[33,1246,290],{"class":90},[33,1248,217],{"class":43},[33,1250,822],{"class":50},[33,1252,223],{"class":43},[33,1254,272],{"class":80},[33,1256,943],{"class":43},[33,1258,1259,1261,1264,1266],{"class":35,"line":54},[33,1260,211],{"class":43},[33,1262,1263],{"class":90},"clearFieldErrors",[33,1265,1205],{"class":43},[33,1267,1268],{"class":232},"\u002F\u002F or form.resetField('posts') for full clean slate\n",[14,1270,1271,1272,1275],{},"For large rearrangements, ",[30,1273,1274],{},"form.resetField('posts')"," rebuilds the\nsubtree cleanly.",[18,1277,1279],{"id":1278},"building-arrays-in-bulk","Building arrays in bulk",[14,1281,1282,1283,1285],{},"Building an array by looping ",[30,1284,214],{}," is O(N²) — each call copies\nthe whole array. For a large seed, assign in one shot:",[23,1287,1289],{"className":25,"code":1288,"language":27,"meta":28,"style":28},"form.setValue('items', nextArray) \u002F\u002F O(N), one assignment\n",[30,1290,1291],{"__ignoreMap":28},[33,1292,1293,1295,1298,1300,1303,1306],{"class":35,"line":36},[33,1294,211],{"class":43},[33,1296,1297],{"class":90},"setValue",[33,1299,217],{"class":43},[33,1301,1302],{"class":50},"'items'",[33,1304,1305],{"class":43},", nextArray) ",[33,1307,1308],{"class":232},"\u002F\u002F O(N), one assignment\n",[14,1310,1311,1313],{},[30,1312,1297],{}," types lead with the full element shape — pass elements\nmatching the schema's element type. If you have partial elements\nfrom a server payload (some keys missing), the runtime\nmergeStructural fills missing keys from the schema's element\ndefault, so the bulk assignment still produces a structurally\ncomplete array. The type system points the IDE at the canonical\n\"give me the whole shape\" pattern at the call site; the runtime\nbackstop catches dynamic \u002F server-shaped inputs.",[18,1315,1317],{"id":1316},"sparse-index-writes-auto-pad","Sparse-index writes auto-pad",[14,1319,1320,1323,1324,1327,1328,1331,1332,1335,1336,1339],{},[30,1321,1322],{},"setValue('posts.21', { ... })"," against an empty ",[30,1325,1326],{},"posts"," array\nfills indices ",[30,1329,1330],{},"0..20"," with the schema's element default before\nwriting index ",[30,1333,1334],{},"21",". The structural-completeness invariant means\nthe array is never sparse on disk — every index ",[30,1337,1338],{},"\u003C length"," is a\nfully-shaped element matching the schema:",[23,1341,1343],{"className":25,"code":1342,"language":27,"meta":28,"style":28},"const form = useForm({\n  schema: z.object({ posts: z.array(z.object({ title: z.string() })) }),\n  key: 'blog',\n})\n\n\u002F\u002F posts is initially []\nform.setValue('posts.5.title', 'sixth post')\n\u002F\u002F posts.length === 6\n\u002F\u002F posts[0..4] are { title: '' } (the schema's element default)\n\u002F\u002F posts[5] is { title: 'sixth post' }\n",[30,1344,1345,1357,1381,1391,1395,1399,1404,1422,1427,1432],{"__ignoreMap":28},[33,1346,1347,1349,1351,1353,1355],{"class":35,"line":36},[33,1348,77],{"class":39},[33,1350,186],{"class":80},[33,1352,84],{"class":39},[33,1354,191],{"class":90},[33,1356,94],{"class":43},[33,1358,1359,1362,1364,1367,1369,1371,1373,1376,1378],{"class":35,"line":54},[33,1360,1361],{"class":43},"  schema: z.",[33,1363,91],{"class":90},[33,1365,1366],{"class":43},"({ posts: z.",[33,1368,103],{"class":90},[33,1370,106],{"class":43},[33,1372,91],{"class":90},[33,1374,1375],{"class":43},"({ title: z.",[33,1377,109],{"class":90},[33,1379,1380],{"class":43},"() })) }),\n",[33,1382,1383,1386,1389],{"class":35,"line":67},[33,1384,1385],{"class":43},"  key: ",[33,1387,1388],{"class":50},"'blog'",[33,1390,846],{"class":43},[33,1392,1393],{"class":35,"line":74},[33,1394,173],{"class":43},[33,1396,1397],{"class":35,"line":97},[33,1398,71],{"emptyLinePlaceholder":70},[33,1400,1401],{"class":35,"line":115},[33,1402,1403],{"class":232},"\u002F\u002F posts is initially []\n",[33,1405,1406,1408,1410,1412,1415,1417,1420],{"class":35,"line":126},[33,1407,211],{"class":43},[33,1409,1297],{"class":90},[33,1411,217],{"class":43},[33,1413,1414],{"class":50},"'posts.5.title'",[33,1416,223],{"class":43},[33,1418,1419],{"class":50},"'sixth post'",[33,1421,943],{"class":43},[33,1423,1424],{"class":35,"line":136},[33,1425,1426],{"class":232},"\u002F\u002F posts.length === 6\n",[33,1428,1429],{"class":35,"line":147},[33,1430,1431],{"class":232},"\u002F\u002F posts[0..4] are { title: '' } (the schema's element default)\n",[33,1433,1434],{"class":35,"line":158},[33,1435,1436],{"class":232},"\u002F\u002F posts[5] is { title: 'sixth post' }\n",[14,1438,1439,1440,1443,1444,1447,1448,1450,1451,1453],{},"This makes ",[30,1441,1442],{},"posts.length"," honest and lets ",[30,1445,1446],{},"v-for"," over ",[30,1449,1326],{},"\nrender N rows without filtering for ",[30,1452,1176],{},". Most consumers\nnever write sparse indices intentionally — the invariant just\nmeans the framework no longer has to guess what to do when one\nslips through.",[1455,1456,1457],"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 pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":28,"searchDepth":54,"depth":54,"links":1459},[1460,1461,1462,1463,1464,1465,1466],{"id":20,"depth":54,"text":21},{"id":443,"depth":54,"text":444},{"id":788,"depth":54,"text":789},{"id":999,"depth":54,"text":1000},{"id":1228,"depth":54,"text":1229},{"id":1278,"depth":54,"text":1279},{"id":1316,"depth":54,"text":1317},"md",{},"\u002Fdocs\u002Frecipes\u002Fdynamic-field-arrays",{"title":5,"description":16},"docs\u002Frecipes\u002Fdynamic-field-arrays","T5FEJ5Ob_xx4yyMJJBO2AsnSNvss2N5wp3PslWkYDCE",1777934136366]