[{"data":1,"prerenderedAt":711},["ShallowReactive",2],{"content-\u002Fdocs\u002Fsubmitting\u002Fserver-side-errors":3},{"id":4,"title":5,"body":6,"description":691,"extension":692,"meta":693,"metaRows":694,"navigation":141,"path":706,"seo":707,"source":708,"stem":709,"__hash__":710},"docs\u002Fdocs\u002Fsubmitting\u002Fserver-side-errors.md","Server-side errors",{"type":7,"value":8,"toc":683},"minimark",[9,13,25,28,58,62,67,70,107,307,312,369,383,403,413,417,420,470,538,542,545,582,589,593,600,607,649,653,679],[10,11,5],"h1",{"id":12},"server-side-errors",[14,15,16],"blockquote",{},[17,18,19,20,24],"p",{},"Treat API failures the same as schema failures: same reactive surface, same ",[21,22,23],"code",{},"firstError"," reads, same focus \u002F scroll behavior on submit.",[26,27],"docs-meta-table",{},[17,29,30,31,34,35,38,39,42,43,46,47,50,51,54,55,57],{},"Submit with ",[21,32,33],{},"taken@example.com"," as the email and ",[21,36,37],{},"admin"," as the username to watch the simulated server response route through ",[21,40,41],{},"parseApiErrors"," → ",[21,44,45],{},"setFieldErrors",". The errors land at ",[21,48,49],{},"errors.email"," and ",[21,52,53],{},"errors.username",", the field-level ",[21,56,23],{}," surfaces them next to the inputs, and the form-level focus pull treats them like any other invalid path. Submit with different values to see the success path fire.",[59,60],"docs-demo",{"label":61,"slug":12},"Server Errors Demo",[63,64,66],"h2",{"id":65},"the-flow","The flow",[17,68,69],{},"A successful round-trip lands a value at the form; a failed one needs to land an error at the right path. The three pieces:",[71,72,73,87,99],"ol",{},[74,75,76,82,83,86],"li",{},[77,78,79],"strong",{},[21,80,81],{},"parseApiErrors(envelope, { formKey })",": normalizes the server response into ",[21,84,85],{},"ValidationError[]",".",[74,88,89,94,95,98],{},[77,90,91],{},[21,92,93],{},"form.setFieldErrors(errors)"," (or ",[21,96,97],{},"form.addFieldErrors","): mounts the parsed errors into the form's reactive surface.",[74,100,101,106],{},[77,102,103],{},[21,104,105],{},"form.clearFieldErrors(path?)",": drops them when the user starts editing or the next submit fires.",[108,109,114],"pre",{"className":110,"code":111,"language":112,"meta":113,"style":113},"language-ts shiki shiki-themes github-light github-dark","import { parseApiErrors } from 'attaform'\n\nconst onSubmit = form.handleSubmit(async (values) => {\n  form.clearFieldErrors()\n  const response = await api.signup(values)\n\n  if (!response.ok) {\n    const parsed = parseApiErrors(response, { formKey: form.key })\n    if (parsed.ok) {\n      form.setFieldErrors(parsed.errors)\n      return\n    }\n  }\n  \u002F\u002F success path\n})\n","ts","",[21,115,116,136,143,185,197,220,225,239,256,265,276,282,288,294,301],{"__ignoreMap":113},[117,118,121,125,129,132],"span",{"class":119,"line":120},"line",1,[117,122,124],{"class":123},"szBVR","import",[117,126,128],{"class":127},"sVt8B"," { parseApiErrors } ",[117,130,131],{"class":123},"from",[117,133,135],{"class":134},"sZZnC"," 'attaform'\n",[117,137,139],{"class":119,"line":138},2,[117,140,142],{"emptyLinePlaceholder":141},true,"\n",[117,144,146,149,153,156,159,163,166,169,172,176,179,182],{"class":119,"line":145},3,[117,147,148],{"class":123},"const",[117,150,152],{"class":151},"sj4cs"," onSubmit",[117,154,155],{"class":123}," =",[117,157,158],{"class":127}," form.",[117,160,162],{"class":161},"sScJk","handleSubmit",[117,164,165],{"class":127},"(",[117,167,168],{"class":123},"async",[117,170,171],{"class":127}," (",[117,173,175],{"class":174},"s4XuR","values",[117,177,178],{"class":127},") ",[117,180,181],{"class":123},"=>",[117,183,184],{"class":127}," {\n",[117,186,188,191,194],{"class":119,"line":187},4,[117,189,190],{"class":127},"  form.",[117,192,193],{"class":161},"clearFieldErrors",[117,195,196],{"class":127},"()\n",[117,198,200,203,206,208,211,214,217],{"class":119,"line":199},5,[117,201,202],{"class":123},"  const",[117,204,205],{"class":151}," response",[117,207,155],{"class":123},[117,209,210],{"class":123}," await",[117,212,213],{"class":127}," api.",[117,215,216],{"class":161},"signup",[117,218,219],{"class":127},"(values)\n",[117,221,223],{"class":119,"line":222},6,[117,224,142],{"emptyLinePlaceholder":141},[117,226,228,231,233,236],{"class":119,"line":227},7,[117,229,230],{"class":123},"  if",[117,232,171],{"class":127},[117,234,235],{"class":123},"!",[117,237,238],{"class":127},"response.ok) {\n",[117,240,242,245,248,250,253],{"class":119,"line":241},8,[117,243,244],{"class":123},"    const",[117,246,247],{"class":151}," parsed",[117,249,155],{"class":123},[117,251,252],{"class":161}," parseApiErrors",[117,254,255],{"class":127},"(response, { formKey: form.key })\n",[117,257,259,262],{"class":119,"line":258},9,[117,260,261],{"class":123},"    if",[117,263,264],{"class":127}," (parsed.ok) {\n",[117,266,268,271,273],{"class":119,"line":267},10,[117,269,270],{"class":127},"      form.",[117,272,45],{"class":161},[117,274,275],{"class":127},"(parsed.errors)\n",[117,277,279],{"class":119,"line":278},11,[117,280,281],{"class":123},"      return\n",[117,283,285],{"class":119,"line":284},12,[117,286,287],{"class":127},"    }\n",[117,289,291],{"class":119,"line":290},13,[117,292,293],{"class":127},"  }\n",[117,295,297],{"class":119,"line":296},14,[117,298,300],{"class":299},"sJ8bj","  \u002F\u002F success path\n",[117,302,304],{"class":119,"line":303},15,[117,305,306],{"class":127},"})\n",[63,308,310],{"id":309},"parseapierrors",[21,311,41],{},[108,313,315],{"className":110,"code":314,"language":112,"meta":113,"style":113},"parseApiErrors(\n  envelope: ApiErrorEnvelope,\n  options: { formKey: string }\n): { ok: true; errors: ValidationError[] } | { ok: false; reason: string }\n",[21,316,317,324,329,334],{"__ignoreMap":113},[117,318,319,321],{"class":119,"line":120},[117,320,41],{"class":161},[117,322,323],{"class":127},"(\n",[117,325,326],{"class":119,"line":138},[117,327,328],{"class":127},"  envelope: ApiErrorEnvelope,\n",[117,330,331],{"class":119,"line":145},[117,332,333],{"class":127},"  options: { formKey: string }\n",[117,335,336,339,342,345,348,351,354,357,360,363,366],{"class":119,"line":187},[117,337,338],{"class":127},"): { ok: ",[117,340,341],{"class":151},"true",[117,343,344],{"class":127},"; errors: ValidationError[] } ",[117,346,347],{"class":123},"|",[117,349,350],{"class":127}," { ",[117,352,353],{"class":161},"ok",[117,355,356],{"class":127},": ",[117,358,359],{"class":151},"false",[117,361,362],{"class":127},"; ",[117,364,365],{"class":161},"reason",[117,367,368],{"class":127},": string }\n",[17,370,371,374,375,378,379,382],{},[21,372,373],{},"ApiErrorEnvelope"," is the shape the server returns: a wrapped object with a ",[21,376,377],{},"details"," array of ",[21,380,381],{},"{ path, message }"," entries. The parser:",[384,385,386,393,400],"ul",{},[74,387,388,389,392],{},"Validates the envelope shape; returns ",[21,390,391],{},"{ ok: false, reason }"," when it doesn't conform.",[74,394,395,396,399],{},"Maps each entry's ",[21,397,398],{},"path"," to the form's path tuple.",[74,401,402],{},"Stamps each error with the form key for cross-form isolation.",[17,404,405,406,409,410,412],{},"When the response is a plain ",[21,407,408],{},"200 { ok: true }",", skip the call entirely; ",[21,411,41],{}," is for failure paths.",[63,414,416],{"id":415},"mounting-errors","Mounting errors",[17,418,419],{},"Three methods land parsed errors into the form:",[421,422,423,436],"table",{},[424,425,426],"thead",{},[427,428,429,433],"tr",{},[430,431,432],"th",{},"Method",[430,434,435],{},"Use",[437,438,439,450,460],"tbody",{},[427,440,441,447],{},[442,443,444],"td",{},[21,445,446],{},"setFieldErrors(errors)",[442,448,449],{},"Replace the current error set with the supplied list. Use after a fresh server round-trip.",[427,451,452,457],{},[442,453,454],{},[21,455,456],{},"addFieldErrors(errors)",[442,458,459],{},"Append to the existing set without clearing prior entries. Use when reporting an additional issue alongside existing schema errors.",[427,461,462,467],{},[442,463,464],{},[21,465,466],{},"clearFieldErrors(path?)",[442,468,469],{},"Drop all errors (no arg) or just one path's errors. Use when the user edits a field that previously had a server error.",[108,471,473],{"className":110,"code":472,"language":112,"meta":113,"style":113},"form.setFieldErrors(parsedErrors)\nform.addFieldErrors([{ path: ['email'], message: 'Already registered', code: 'custom' }])\nform.clearFieldErrors('email')\nform.clearFieldErrors() \u002F\u002F clear everything\n",[21,474,475,485,513,526],{"__ignoreMap":113},[117,476,477,480,482],{"class":119,"line":120},[117,478,479],{"class":127},"form.",[117,481,45],{"class":161},[117,483,484],{"class":127},"(parsedErrors)\n",[117,486,487,489,492,495,498,501,504,507,510],{"class":119,"line":138},[117,488,479],{"class":127},[117,490,491],{"class":161},"addFieldErrors",[117,493,494],{"class":127},"([{ path: [",[117,496,497],{"class":134},"'email'",[117,499,500],{"class":127},"], message: ",[117,502,503],{"class":134},"'Already registered'",[117,505,506],{"class":127},", code: ",[117,508,509],{"class":134},"'custom'",[117,511,512],{"class":127}," }])\n",[117,514,515,517,519,521,523],{"class":119,"line":145},[117,516,479],{"class":127},[117,518,193],{"class":161},[117,520,165],{"class":127},[117,522,497],{"class":134},[117,524,525],{"class":127},")\n",[117,527,528,530,532,535],{"class":119,"line":187},[117,529,479],{"class":127},[117,531,193],{"class":161},[117,533,534],{"class":127},"() ",[117,536,537],{"class":299},"\u002F\u002F clear everything\n",[63,539,541],{"id":540},"same-reactive-surface","Same reactive surface",[17,543,544],{},"Once mounted, server errors are indistinguishable from schema errors at the read surfaces:",[384,546,547,556,562,576],{},[74,548,549,552,553,555],{},[21,550,551],{},"form.errors.email"," returns the ",[21,554,85],{}," (server or schema, same shape).",[74,557,558,561],{},[21,559,560],{},"form.fields.email.firstError"," returns the first one.",[74,563,564,567,568,575],{},[21,565,566],{},"form.fields.email.showErrors"," gates display per the ",[569,570,572],"a",{"href":571},"\u002Fdocs\u002Fvalidation\u002Fshowing-errors",[21,573,574],{},"getDisplayState"," predicate.",[74,577,578,581],{},[21,579,580],{},"form.focusFirstError()"," pulls focus to the first server error just like a schema one.",[17,583,584,585,588],{},"No special \"this is a server error\" surface in the template. The render code reads ",[21,586,587],{},"form.fields.\u003Cpath>.firstError?.message"," the same way for both kinds.",[63,590,592],{"id":591},"auto-clear-on-edit","Auto-clear on edit",[17,594,595,596,599],{},"By default, editing a field after a server error landed at that path does NOT auto-clear the error: it'll persist until the next submit re-runs or ",[21,597,598],{},"form.clearFieldErrors"," fires explicitly. Most servers want a fresh round-trip before the error is \"cleared,\" so this matches the network round-trip semantics.",[17,601,602,603,606],{},"For \"clear on edit\" UX, hook a watcher on the path and call ",[21,604,605],{},"clearFieldErrors(path)",":",[108,608,610],{"className":110,"code":609,"language":112,"meta":113,"style":113},"watch(\n  () => form.values.email,\n  () => form.clearFieldErrors('email')\n)\n",[21,611,612,619,629,645],{"__ignoreMap":113},[117,613,614,617],{"class":119,"line":120},[117,615,616],{"class":161},"watch",[117,618,323],{"class":127},[117,620,621,624,626],{"class":119,"line":138},[117,622,623],{"class":127},"  () ",[117,625,181],{"class":123},[117,627,628],{"class":127}," form.values.email,\n",[117,630,631,633,635,637,639,641,643],{"class":119,"line":145},[117,632,623],{"class":127},[117,634,181],{"class":123},[117,636,158],{"class":127},[117,638,193],{"class":161},[117,640,165],{"class":127},[117,642,497],{"class":134},[117,644,525],{"class":127},[117,646,647],{"class":119,"line":187},[117,648,525],{"class":127},[63,650,652],{"id":651},"where-to-next","Where to next",[384,654,655,663,670],{},[74,656,657,662],{},[569,658,660],{"href":659},"\u002Fdocs\u002Fsubmitting\u002Fhandle-submit",[21,661,162],{},": the dispatch surface server errors plug into.",[74,664,665,669],{},[569,666,668],{"href":667},"\u002Fdocs\u002Fsubmitting\u002Ffocus-scroll","Focus & scroll on invalid submit",": same machinery, applied to server errors after mount.",[74,671,672,678],{},[569,673,675],{"href":674},"\u002Fdocs\u002Freading-the-form\u002Ferrors",[21,676,677],{},"errors",": the reactive read surface for every error, server or schema.",[680,681,682],"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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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);}",{"title":113,"searchDepth":138,"depth":138,"links":684},[685,686,687,688,689,690],{"id":65,"depth":138,"text":66},{"id":309,"depth":138,"text":41},{"id":415,"depth":138,"text":416},{"id":540,"depth":138,"text":541},{"id":591,"depth":138,"text":592},{"id":651,"depth":138,"text":652},"parseApiErrors translates API failure responses into ValidationError[]; setFieldErrors \u002F addFieldErrors \u002F clearFieldErrors mount them into the same reactive surface the validator uses.","md",{},[695,698,700,703],{"label":696,"value":697},"Category","Helper + Return methods",{"label":699,"value":81,"kind":21},"Parser",{"label":701,"value":702,"kind":21},"Mounters","setFieldErrors · addFieldErrors · clearFieldErrors",{"label":704,"value":705,"kind":21},"Returns","{ ok: true, errors } | { ok: false, reason }","\u002Fdocs\u002Fsubmitting\u002Fserver-side-errors",{"title":5,"description":691},null,"docs\u002Fsubmitting\u002Fserver-side-errors","PUYUdkef7sanvneoce7cvHZ73RJ06PMurtS9LLqNG_I",1780949760689]