[{"data":1,"prerenderedAt":1921},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-controlled-vs-uncontrolled-guide":3},{"id":4,"title":5,"body":6,"description":1905,"difficulty":1906,"extension":1907,"framework":1908,"frameworkSlug":1909,"meta":1910,"navigation":129,"order":126,"path":1912,"qaPath":1913,"seo":1914,"stem":1915,"subtopic":1916,"topic":1917,"topicSlug":1918,"updated":1919,"__hash__":1920},"blog\u002Fblog\u002Freact-controlled-vs-uncontrolled-guide.md","Controlled vs Uncontrolled Components in React — A Complete Guide",{"type":7,"value":8,"toc":1888},"minimark",[9,14,22,43,46,50,60,221,224,244,253,258,270,285,295,385,389,407,477,481,488,661,666,670,701,823,832,884,894,1096,1100,1103,1182,1185,1226,1229,1233,1236,1485,1488,1492,1499,1745,1756,1766,1770,1830,1834,1884],[10,11,13],"h2",{"id":12},"the-core-question-who-owns-the-value","The core question: who owns the value?",[15,16,17,18],"p",{},"Every form element in React has one fundamental question: ",[19,20,21],"strong",{},"who is the source\nof truth for its current value — React state or the DOM?",[23,24,25,37],"ul",{},[26,27,28,31,32,36],"li",{},[19,29,30],{},"Controlled component"," → React state is the source of truth. The ",[33,34,35],"code",{},"value","\nprop drives the DOM. Every keystroke goes through state.",[26,38,39,42],{},[19,40,41],{},"Uncontrolled component"," → The DOM is the source of truth. React sets the\ninitial value and steps back. You read the value only when you need it.",[15,44,45],{},"Everything else follows from this distinction.",[10,47,49],{"id":48},"controlled-components-in-depth","Controlled components in depth",[15,51,52,53,55,56,59],{},"A controlled input is one where ",[33,54,35],{}," is driven by React state and every\nchange is funnelled through ",[33,57,58],{},"onChange",":",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-jsx shiki shiki-themes github-light github-dark","function EmailField() {\n  const [email, setEmail] = useState('')\n\n  return (\n    \u003Cinput\n      type=\"email\"\n      value={email}                           \u002F\u002F state → DOM\n      onChange={e => setEmail(e.target.value)} \u002F\u002F DOM event → state\n    \u002F>\n  )\n}\n","jsx","",[33,68,69,86,124,131,140,150,161,176,203,209,215],{"__ignoreMap":66},[70,71,74,78,82],"span",{"class":72,"line":73},"line",1,[70,75,77],{"class":76},"szBVR","function",[70,79,81],{"class":80},"sScJk"," EmailField",[70,83,85],{"class":84},"sVt8B","() {\n",[70,87,89,92,95,99,102,105,108,111,114,117,121],{"class":72,"line":88},2,[70,90,91],{"class":76},"  const",[70,93,94],{"class":84}," [",[70,96,98],{"class":97},"sj4cs","email",[70,100,101],{"class":84},", ",[70,103,104],{"class":97},"setEmail",[70,106,107],{"class":84},"] ",[70,109,110],{"class":76},"=",[70,112,113],{"class":80}," useState",[70,115,116],{"class":84},"(",[70,118,120],{"class":119},"sZZnC","''",[70,122,123],{"class":84},")\n",[70,125,127],{"class":72,"line":126},3,[70,128,130],{"emptyLinePlaceholder":129},true,"\n",[70,132,134,137],{"class":72,"line":133},4,[70,135,136],{"class":76},"  return",[70,138,139],{"class":84}," (\n",[70,141,143,146],{"class":72,"line":142},5,[70,144,145],{"class":84},"    \u003C",[70,147,149],{"class":148},"s9eBZ","input\n",[70,151,153,156,158],{"class":72,"line":152},6,[70,154,155],{"class":80},"      type",[70,157,110],{"class":76},[70,159,160],{"class":119},"\"email\"\n",[70,162,164,167,169,172],{"class":72,"line":163},7,[70,165,166],{"class":80},"      value",[70,168,110],{"class":76},[70,170,171],{"class":84},"{email}                           ",[70,173,175],{"class":174},"sJ8bj","\u002F\u002F state → DOM\n",[70,177,179,182,184,187,191,194,197,200],{"class":72,"line":178},8,[70,180,181],{"class":80},"      onChange",[70,183,110],{"class":76},[70,185,186],{"class":84},"{",[70,188,190],{"class":189},"s4XuR","e",[70,192,193],{"class":76}," =>",[70,195,196],{"class":80}," setEmail",[70,198,199],{"class":84},"(e.target.value)} ",[70,201,202],{"class":174},"\u002F\u002F DOM event → state\n",[70,204,206],{"class":72,"line":205},9,[70,207,208],{"class":84},"    \u002F>\n",[70,210,212],{"class":72,"line":211},10,[70,213,214],{"class":84},"  )\n",[70,216,218],{"class":72,"line":217},11,[70,219,220],{"class":84},"}\n",[15,222,223],{},"This creates a tight feedback loop. React knows the field's value at all times\nand can:",[23,225,226,229,232,235,241],{},[26,227,228],{},"Validate on every keystroke and show\u002Fhide error messages",[26,230,231],{},"Disable a submit button until the form is complete",[26,233,234],{},"Transform input (uppercase, format phone numbers, trim whitespace)",[26,236,237,238],{},"Reset the form by simply calling ",[33,239,240],{},"setState(initialValues)",[26,242,243],{},"Read the complete form data in the submit handler without touching the DOM",[15,245,246,247,249,250,252],{},"The cost is that you must wire ",[33,248,35],{}," + ",[33,251,58],{}," for every field. For large\nforms this is repetitive — which is why libraries like React Hook Form exist.",[254,255,257],"h3",{"id":256},"the-read-only-trap","The read-only trap",[15,259,260,261,263,264,266,267,269],{},"If you pass ",[33,262,35],{}," without ",[33,265,58],{},", React freezes the input — users type\nbut the DOM value is reset to ",[33,268,35],{}," on every render. React warns:",[271,272,273],"blockquote",{},[15,274,275],{},[276,277,278,279,281,282,284],"em",{},"\"You provided a ",[33,280,35],{}," prop to a form field without an ",[33,283,58],{}," handler.\nThis will render a read-only field.\"",[15,286,287,288,290,291,294],{},"Fix it with ",[33,289,58],{},", or use ",[33,292,293],{},"readOnly"," if the intent is truly read-only:",[61,296,298],{"className":63,"code":297,"language":65,"meta":66,"style":66},"\u002F\u002F ❌ Frozen — user can't type\n\u003Cinput value={someValue} \u002F>\n\n\u002F\u002F ✅ Controlled\n\u003Cinput value={someValue} onChange={e => setValue(e.target.value)} \u002F>\n\n\u002F\u002F ✅ Intentionally read-only\n\u003Cinput value={someValue} readOnly \u002F>\n",[33,299,300,305,321,325,330,359,363,368],{"__ignoreMap":66},[70,301,302],{"class":72,"line":73},[70,303,304],{"class":174},"\u002F\u002F ❌ Frozen — user can't type\n",[70,306,307,310,313,316,318],{"class":72,"line":88},[70,308,309],{"class":84},"\u003C",[70,311,312],{"class":148},"input",[70,314,315],{"class":80}," value",[70,317,110],{"class":76},[70,319,320],{"class":84},"{someValue} \u002F>\n",[70,322,323],{"class":72,"line":126},[70,324,130],{"emptyLinePlaceholder":129},[70,326,327],{"class":72,"line":133},[70,328,329],{"class":174},"\u002F\u002F ✅ Controlled\n",[70,331,332,334,336,338,340,343,345,347,349,351,353,356],{"class":72,"line":142},[70,333,309],{"class":84},[70,335,312],{"class":148},[70,337,315],{"class":80},[70,339,110],{"class":76},[70,341,342],{"class":84},"{someValue} ",[70,344,58],{"class":80},[70,346,110],{"class":76},[70,348,186],{"class":84},[70,350,190],{"class":189},[70,352,193],{"class":76},[70,354,355],{"class":80}," setValue",[70,357,358],{"class":84},"(e.target.value)} \u002F>\n",[70,360,361],{"class":72,"line":152},[70,362,130],{"emptyLinePlaceholder":129},[70,364,365],{"class":72,"line":163},[70,366,367],{"class":174},"\u002F\u002F ✅ Intentionally read-only\n",[70,369,370,372,374,376,378,380,382],{"class":72,"line":178},[70,371,309],{"class":84},[70,373,312],{"class":148},[70,375,315],{"class":80},[70,377,110],{"class":76},[70,379,342],{"class":84},[70,381,293],{"class":80},[70,383,384],{"class":84}," \u002F>\n",[254,386,388],{"id":387},"starting-controlled-with-an-empty-string","Starting controlled with an empty string",[15,390,391,392,395,396,399,400,403,404,406],{},"Never initialise controlled state with ",[33,393,394],{},"null"," or ",[33,397,398],{},"undefined",". That starts the\ninput as ",[276,401,402],{},"uncontrolled"," (React treats a ",[33,405,394],{}," value as \"no value\"), and when\nyou later assign a real string React will warn about switching from\nuncontrolled to controlled.",[61,408,410],{"className":63,"code":409,"language":65,"meta":66,"style":66},"\u002F\u002F ❌ Starts uncontrolled → triggers warning when set to a real string\nconst [text, setText] = useState(null)\n\n\u002F\u002F ✅ Always start with a string (even empty)\nconst [text, setText] = useState('')\n",[33,411,412,417,444,448,453],{"__ignoreMap":66},[70,413,414],{"class":72,"line":73},[70,415,416],{"class":174},"\u002F\u002F ❌ Starts uncontrolled → triggers warning when set to a real string\n",[70,418,419,422,424,427,429,432,434,436,438,440,442],{"class":72,"line":88},[70,420,421],{"class":76},"const",[70,423,94],{"class":84},[70,425,426],{"class":97},"text",[70,428,101],{"class":84},[70,430,431],{"class":97},"setText",[70,433,107],{"class":84},[70,435,110],{"class":76},[70,437,113],{"class":80},[70,439,116],{"class":84},[70,441,394],{"class":97},[70,443,123],{"class":84},[70,445,446],{"class":72,"line":126},[70,447,130],{"emptyLinePlaceholder":129},[70,449,450],{"class":72,"line":133},[70,451,452],{"class":174},"\u002F\u002F ✅ Always start with a string (even empty)\n",[70,454,455,457,459,461,463,465,467,469,471,473,475],{"class":72,"line":142},[70,456,421],{"class":76},[70,458,94],{"class":84},[70,460,426],{"class":97},[70,462,101],{"class":84},[70,464,431],{"class":97},[70,466,107],{"class":84},[70,468,110],{"class":76},[70,470,113],{"class":80},[70,472,116],{"class":84},[70,474,120],{"class":119},[70,476,123],{"class":84},[10,478,480],{"id":479},"uncontrolled-components-in-depth","Uncontrolled components in depth",[15,482,483,484,487],{},"An uncontrolled input lets the browser manage its own value. You attach a\n",[33,485,486],{},"ref"," to read the value imperatively when needed:",[61,489,491],{"className":63,"code":490,"language":65,"meta":66,"style":66},"function SearchBox() {\n  const inputRef = useRef(null)\n\n  function handleSubmit(e) {\n    e.preventDefault()\n    performSearch(inputRef.current.value)  \u002F\u002F read on demand\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput ref={inputRef} defaultValue=\"\" \u002F>\n      \u003Cbutton type=\"submit\">Search\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n",[33,492,493,502,521,525,540,551,562,567,571,577,592,617,641,651,656],{"__ignoreMap":66},[70,494,495,497,500],{"class":72,"line":73},[70,496,77],{"class":76},[70,498,499],{"class":80}," SearchBox",[70,501,85],{"class":84},[70,503,504,506,509,512,515,517,519],{"class":72,"line":88},[70,505,91],{"class":76},[70,507,508],{"class":97}," inputRef",[70,510,511],{"class":76}," =",[70,513,514],{"class":80}," useRef",[70,516,116],{"class":84},[70,518,394],{"class":97},[70,520,123],{"class":84},[70,522,523],{"class":72,"line":126},[70,524,130],{"emptyLinePlaceholder":129},[70,526,527,530,533,535,537],{"class":72,"line":133},[70,528,529],{"class":76},"  function",[70,531,532],{"class":80}," handleSubmit",[70,534,116],{"class":84},[70,536,190],{"class":189},[70,538,539],{"class":84},") {\n",[70,541,542,545,548],{"class":72,"line":142},[70,543,544],{"class":84},"    e.",[70,546,547],{"class":80},"preventDefault",[70,549,550],{"class":84},"()\n",[70,552,553,556,559],{"class":72,"line":152},[70,554,555],{"class":80},"    performSearch",[70,557,558],{"class":84},"(inputRef.current.value)  ",[70,560,561],{"class":174},"\u002F\u002F read on demand\n",[70,563,564],{"class":72,"line":163},[70,565,566],{"class":84},"  }\n",[70,568,569],{"class":72,"line":178},[70,570,130],{"emptyLinePlaceholder":129},[70,572,573,575],{"class":72,"line":205},[70,574,136],{"class":76},[70,576,139],{"class":84},[70,578,579,581,584,587,589],{"class":72,"line":211},[70,580,145],{"class":84},[70,582,583],{"class":148},"form",[70,585,586],{"class":80}," onSubmit",[70,588,110],{"class":76},[70,590,591],{"class":84},"{handleSubmit}>\n",[70,593,594,597,599,602,604,607,610,612,615],{"class":72,"line":217},[70,595,596],{"class":84},"      \u003C",[70,598,312],{"class":148},[70,600,601],{"class":80}," ref",[70,603,110],{"class":76},[70,605,606],{"class":84},"{inputRef} ",[70,608,609],{"class":80},"defaultValue",[70,611,110],{"class":76},[70,613,614],{"class":119},"\"\"",[70,616,384],{"class":84},[70,618,620,622,625,628,630,633,636,638],{"class":72,"line":619},12,[70,621,596],{"class":84},[70,623,624],{"class":148},"button",[70,626,627],{"class":80}," type",[70,629,110],{"class":76},[70,631,632],{"class":119},"\"submit\"",[70,634,635],{"class":84},">Search\u003C\u002F",[70,637,624],{"class":148},[70,639,640],{"class":84},">\n",[70,642,644,647,649],{"class":72,"line":643},13,[70,645,646],{"class":84},"    \u003C\u002F",[70,648,583],{"class":148},[70,650,640],{"class":84},[70,652,654],{"class":72,"line":653},14,[70,655,214],{"class":84},[70,657,659],{"class":72,"line":658},15,[70,660,220],{"class":84},[15,662,663,665],{},[33,664,609],{}," sets the initial value without making the input controlled.\nAfter that, the DOM owns the value.",[254,667,669],{"id":668},"when-uncontrolled-is-appropriate","When uncontrolled is appropriate",[23,671,672,678,695],{},[26,673,674,677],{},[19,675,676],{},"Submit-only forms"," where you need the value once, not on every keystroke",[26,679,680,683,684,687,688,691,692,694],{},[19,681,682],{},"File inputs"," — ",[33,685,686],{},"\u003Cinput type=\"file\">"," is ",[276,689,690],{},"always"," uncontrolled. The\nbrowser controls the file path for security; you can never set ",[33,693,35],{}," on it",[26,696,697,700],{},[19,698,699],{},"Integrating third-party DOM libraries"," that manipulate inputs directly\n(rich text editors, date pickers that aren't React-aware)",[61,702,704],{"className":63,"code":703,"language":65,"meta":66,"style":66},"\u002F\u002F File input — must use ref, value prop is not supported\nfunction FileUpload() {\n  const fileRef = useRef(null)\n\n  function upload() {\n    const file = fileRef.current.files[0]\n    if (file) formData.append('file', file)\n  }\n\n  return \u003Cinput type=\"file\" ref={fileRef} \u002F>\n}\n",[33,705,706,711,720,737,741,750,769,788,792,796,819],{"__ignoreMap":66},[70,707,708],{"class":72,"line":73},[70,709,710],{"class":174},"\u002F\u002F File input — must use ref, value prop is not supported\n",[70,712,713,715,718],{"class":72,"line":88},[70,714,77],{"class":76},[70,716,717],{"class":80}," FileUpload",[70,719,85],{"class":84},[70,721,722,724,727,729,731,733,735],{"class":72,"line":126},[70,723,91],{"class":76},[70,725,726],{"class":97}," fileRef",[70,728,511],{"class":76},[70,730,514],{"class":80},[70,732,116],{"class":84},[70,734,394],{"class":97},[70,736,123],{"class":84},[70,738,739],{"class":72,"line":133},[70,740,130],{"emptyLinePlaceholder":129},[70,742,743,745,748],{"class":72,"line":142},[70,744,529],{"class":76},[70,746,747],{"class":80}," upload",[70,749,85],{"class":84},[70,751,752,755,758,760,763,766],{"class":72,"line":152},[70,753,754],{"class":76},"    const",[70,756,757],{"class":97}," file",[70,759,511],{"class":76},[70,761,762],{"class":84}," fileRef.current.files[",[70,764,765],{"class":97},"0",[70,767,768],{"class":84},"]\n",[70,770,771,774,777,780,782,785],{"class":72,"line":163},[70,772,773],{"class":76},"    if",[70,775,776],{"class":84}," (file) formData.",[70,778,779],{"class":80},"append",[70,781,116],{"class":84},[70,783,784],{"class":119},"'file'",[70,786,787],{"class":84},", file)\n",[70,789,790],{"class":72,"line":178},[70,791,566],{"class":84},[70,793,794],{"class":72,"line":205},[70,795,130],{"emptyLinePlaceholder":129},[70,797,798,800,803,805,807,809,812,814,816],{"class":72,"line":211},[70,799,136],{"class":76},[70,801,802],{"class":84}," \u003C",[70,804,312],{"class":148},[70,806,627],{"class":80},[70,808,110],{"class":76},[70,810,811],{"class":119},"\"file\"",[70,813,601],{"class":80},[70,815,110],{"class":76},[70,817,818],{"class":84},"{fileRef} \u002F>\n",[70,820,821],{"class":72,"line":217},[70,822,220],{"class":84},[10,824,826,828,829,831],{"id":825},"value-vs-defaultvalue-the-complete-picture",[33,827,35],{}," vs ",[33,830,609],{}," — the complete picture",[833,834,835,848],"table",{},[836,837,838],"thead",{},[839,840,841,845],"tr",{},[842,843,844],"th",{},"Prop",[842,846,847],{},"Effect",[849,850,851,865,876],"tbody",{},[839,852,853,858],{},[854,855,856],"td",{},[33,857,35],{},[854,859,860,861,864],{},"Input is ",[19,862,863],{},"controlled"," — React drives the DOM value on every render",[839,866,867,871],{},[854,868,869],{},[33,870,609],{},[854,872,860,873,875],{},[19,874,402],{}," — sets the initial DOM value, then DOM takes over",[839,877,878,881],{},[854,879,880],{},"Neither",[854,882,883],{},"Input is uncontrolled with no initial value",[15,885,886,887,890,891,59],{},"The same pattern applies to ",[33,888,889],{},"\u003Ctextarea>"," and ",[33,892,893],{},"\u003Cselect>",[61,895,897],{"className":63,"code":896,"language":65,"meta":66,"style":66},"\u002F\u002F Controlled textarea\n\u003Ctextarea value={text} onChange={e => setText(e.target.value)} \u002F>\n\n\u002F\u002F Controlled select — value matches an \u003Coption>'s value\n\u003Cselect value={chosen} onChange={e => setChosen(e.target.value)}>\n  \u003Coption value=\"a\">A\u003C\u002Foption>\n  \u003Coption value=\"b\">B\u003C\u002Foption>\n\u003C\u002Fselect>\n\n\u002F\u002F Uncontrolled select with default\n\u003Cselect defaultValue=\"b\" ref={selectRef}>\n  \u003Coption value=\"a\">A\u003C\u002Foption>\n  \u003Coption value=\"b\">B\u003C\u002Foption>\n\u003C\u002Fselect>\n",[33,898,899,904,933,937,942,972,994,1014,1023,1027,1032,1052,1070,1088],{"__ignoreMap":66},[70,900,901],{"class":72,"line":73},[70,902,903],{"class":174},"\u002F\u002F Controlled textarea\n",[70,905,906,908,911,913,915,918,920,922,924,926,928,931],{"class":72,"line":88},[70,907,309],{"class":84},[70,909,910],{"class":148},"textarea",[70,912,315],{"class":80},[70,914,110],{"class":76},[70,916,917],{"class":84},"{text} ",[70,919,58],{"class":80},[70,921,110],{"class":76},[70,923,186],{"class":84},[70,925,190],{"class":189},[70,927,193],{"class":76},[70,929,930],{"class":80}," setText",[70,932,358],{"class":84},[70,934,935],{"class":72,"line":126},[70,936,130],{"emptyLinePlaceholder":129},[70,938,939],{"class":72,"line":133},[70,940,941],{"class":174},"\u002F\u002F Controlled select — value matches an \u003Coption>'s value\n",[70,943,944,946,949,951,953,956,958,960,962,964,966,969],{"class":72,"line":142},[70,945,309],{"class":84},[70,947,948],{"class":148},"select",[70,950,315],{"class":80},[70,952,110],{"class":76},[70,954,955],{"class":84},"{chosen} ",[70,957,58],{"class":80},[70,959,110],{"class":76},[70,961,186],{"class":84},[70,963,190],{"class":189},[70,965,193],{"class":76},[70,967,968],{"class":80}," setChosen",[70,970,971],{"class":84},"(e.target.value)}>\n",[70,973,974,977,980,982,984,987,990,992],{"class":72,"line":152},[70,975,976],{"class":84},"  \u003C",[70,978,979],{"class":148},"option",[70,981,315],{"class":80},[70,983,110],{"class":76},[70,985,986],{"class":119},"\"a\"",[70,988,989],{"class":84},">A\u003C\u002F",[70,991,979],{"class":148},[70,993,640],{"class":84},[70,995,996,998,1000,1002,1004,1007,1010,1012],{"class":72,"line":163},[70,997,976],{"class":84},[70,999,979],{"class":148},[70,1001,315],{"class":80},[70,1003,110],{"class":76},[70,1005,1006],{"class":119},"\"b\"",[70,1008,1009],{"class":84},">B\u003C\u002F",[70,1011,979],{"class":148},[70,1013,640],{"class":84},[70,1015,1016,1019,1021],{"class":72,"line":178},[70,1017,1018],{"class":84},"\u003C\u002F",[70,1020,948],{"class":148},[70,1022,640],{"class":84},[70,1024,1025],{"class":72,"line":205},[70,1026,130],{"emptyLinePlaceholder":129},[70,1028,1029],{"class":72,"line":211},[70,1030,1031],{"class":174},"\u002F\u002F Uncontrolled select with default\n",[70,1033,1034,1036,1038,1041,1043,1045,1047,1049],{"class":72,"line":217},[70,1035,309],{"class":84},[70,1037,948],{"class":148},[70,1039,1040],{"class":80}," defaultValue",[70,1042,110],{"class":76},[70,1044,1006],{"class":119},[70,1046,601],{"class":80},[70,1048,110],{"class":76},[70,1050,1051],{"class":84},"{selectRef}>\n",[70,1053,1054,1056,1058,1060,1062,1064,1066,1068],{"class":72,"line":619},[70,1055,976],{"class":84},[70,1057,979],{"class":148},[70,1059,315],{"class":80},[70,1061,110],{"class":76},[70,1063,986],{"class":119},[70,1065,989],{"class":84},[70,1067,979],{"class":148},[70,1069,640],{"class":84},[70,1071,1072,1074,1076,1078,1080,1082,1084,1086],{"class":72,"line":643},[70,1073,976],{"class":84},[70,1075,979],{"class":148},[70,1077,315],{"class":80},[70,1079,110],{"class":76},[70,1081,1006],{"class":119},[70,1083,1009],{"class":84},[70,1085,979],{"class":148},[70,1087,640],{"class":84},[70,1089,1090,1092,1094],{"class":72,"line":653},[70,1091,1018],{"class":84},[70,1093,948],{"class":148},[70,1095,640],{"class":84},[10,1097,1099],{"id":1098},"programmatic-reset","Programmatic reset",[15,1101,1102],{},"Controlled forms reset with a single state update:",[61,1104,1106],{"className":63,"code":1105,"language":65,"meta":66,"style":66},"const EMPTY = { name: '', email: '' }\nconst [form, setForm] = useState(EMPTY)\n\nfunction handleReset() { setForm(EMPTY) }  \u002F\u002F ← all inputs snap to empty\n",[33,1107,1108,1130,1156,1160],{"__ignoreMap":66},[70,1109,1110,1112,1115,1117,1120,1122,1125,1127],{"class":72,"line":73},[70,1111,421],{"class":76},[70,1113,1114],{"class":97}," EMPTY",[70,1116,511],{"class":76},[70,1118,1119],{"class":84}," { name: ",[70,1121,120],{"class":119},[70,1123,1124],{"class":84},", email: ",[70,1126,120],{"class":119},[70,1128,1129],{"class":84}," }\n",[70,1131,1132,1134,1136,1138,1140,1143,1145,1147,1149,1151,1154],{"class":72,"line":88},[70,1133,421],{"class":76},[70,1135,94],{"class":84},[70,1137,583],{"class":97},[70,1139,101],{"class":84},[70,1141,1142],{"class":97},"setForm",[70,1144,107],{"class":84},[70,1146,110],{"class":76},[70,1148,113],{"class":80},[70,1150,116],{"class":84},[70,1152,1153],{"class":97},"EMPTY",[70,1155,123],{"class":84},[70,1157,1158],{"class":72,"line":126},[70,1159,130],{"emptyLinePlaceholder":129},[70,1161,1162,1164,1167,1170,1172,1174,1176,1179],{"class":72,"line":133},[70,1163,77],{"class":76},[70,1165,1166],{"class":80}," handleReset",[70,1168,1169],{"class":84},"() { ",[70,1171,1142],{"class":80},[70,1173,116],{"class":84},[70,1175,1153],{"class":97},[70,1177,1178],{"class":84},") }  ",[70,1180,1181],{"class":174},"\u002F\u002F ← all inputs snap to empty\n",[15,1183,1184],{},"Uncontrolled forms require manually setting each ref:",[61,1186,1188],{"className":63,"code":1187,"language":65,"meta":66,"style":66},"function handleReset() {\n  nameRef.current.value  = ''\n  emailRef.current.value = ''\n  \u002F\u002F repeat for every field\n}\n",[33,1189,1190,1198,1208,1217,1222],{"__ignoreMap":66},[70,1191,1192,1194,1196],{"class":72,"line":73},[70,1193,77],{"class":76},[70,1195,1166],{"class":80},[70,1197,85],{"class":84},[70,1199,1200,1203,1205],{"class":72,"line":88},[70,1201,1202],{"class":84},"  nameRef.current.value  ",[70,1204,110],{"class":76},[70,1206,1207],{"class":119}," ''\n",[70,1209,1210,1213,1215],{"class":72,"line":126},[70,1211,1212],{"class":84},"  emailRef.current.value ",[70,1214,110],{"class":76},[70,1216,1207],{"class":119},[70,1218,1219],{"class":72,"line":133},[70,1220,1221],{"class":174},"  \u002F\u002F repeat for every field\n",[70,1223,1224],{"class":72,"line":142},[70,1225,220],{"class":84},[15,1227,1228],{},"Controlled wins for reset, prefill, and programmatic form manipulation.",[10,1230,1232],{"id":1231},"dynamic-field-lists","Dynamic field lists",[15,1234,1235],{},"Controlled is especially powerful for dynamic forms:",[61,1237,1239],{"className":63,"code":1238,"language":65,"meta":66,"style":66},"function TagsInput() {\n  const [tags, setTags] = useState(['react'])\n\n  return (\n    \u003C>\n      {tags.map((tag, i) => (\n        \u003Cinput\n          key={i}\n          value={tag}\n          onChange={e => setTags(prev =>\n            prev.map((t, idx) => idx === i ? e.target.value : t)\n          )}\n        \u002F>\n      ))}\n      \u003Cbutton onClick={() => setTags(t => [...t, ''])}>+ Add\u003C\u002Fbutton>\n    \u003C\u002F>\n  )\n}\n",[33,1240,1241,1250,1279,1283,1289,1294,1321,1328,1338,1348,1372,1413,1418,1423,1428,1469,1475,1480],{"__ignoreMap":66},[70,1242,1243,1245,1248],{"class":72,"line":73},[70,1244,77],{"class":76},[70,1246,1247],{"class":80}," TagsInput",[70,1249,85],{"class":84},[70,1251,1252,1254,1256,1259,1261,1264,1266,1268,1270,1273,1276],{"class":72,"line":88},[70,1253,91],{"class":76},[70,1255,94],{"class":84},[70,1257,1258],{"class":97},"tags",[70,1260,101],{"class":84},[70,1262,1263],{"class":97},"setTags",[70,1265,107],{"class":84},[70,1267,110],{"class":76},[70,1269,113],{"class":80},[70,1271,1272],{"class":84},"([",[70,1274,1275],{"class":119},"'react'",[70,1277,1278],{"class":84},"])\n",[70,1280,1281],{"class":72,"line":126},[70,1282,130],{"emptyLinePlaceholder":129},[70,1284,1285,1287],{"class":72,"line":133},[70,1286,136],{"class":76},[70,1288,139],{"class":84},[70,1290,1291],{"class":72,"line":142},[70,1292,1293],{"class":84},"    \u003C>\n",[70,1295,1296,1299,1302,1305,1308,1310,1313,1316,1319],{"class":72,"line":152},[70,1297,1298],{"class":84},"      {tags.",[70,1300,1301],{"class":80},"map",[70,1303,1304],{"class":84},"((",[70,1306,1307],{"class":189},"tag",[70,1309,101],{"class":84},[70,1311,1312],{"class":189},"i",[70,1314,1315],{"class":84},") ",[70,1317,1318],{"class":76},"=>",[70,1320,139],{"class":84},[70,1322,1323,1326],{"class":72,"line":163},[70,1324,1325],{"class":84},"        \u003C",[70,1327,149],{"class":148},[70,1329,1330,1333,1335],{"class":72,"line":178},[70,1331,1332],{"class":80},"          key",[70,1334,110],{"class":76},[70,1336,1337],{"class":84},"{i}\n",[70,1339,1340,1343,1345],{"class":72,"line":205},[70,1341,1342],{"class":80},"          value",[70,1344,110],{"class":76},[70,1346,1347],{"class":84},"{tag}\n",[70,1349,1350,1353,1355,1357,1359,1361,1364,1366,1369],{"class":72,"line":211},[70,1351,1352],{"class":80},"          onChange",[70,1354,110],{"class":76},[70,1356,186],{"class":84},[70,1358,190],{"class":189},[70,1360,193],{"class":76},[70,1362,1363],{"class":80}," setTags",[70,1365,116],{"class":84},[70,1367,1368],{"class":189},"prev",[70,1370,1371],{"class":76}," =>\n",[70,1373,1374,1377,1379,1381,1384,1386,1389,1391,1393,1396,1399,1402,1405,1408,1410],{"class":72,"line":217},[70,1375,1376],{"class":84},"            prev.",[70,1378,1301],{"class":80},[70,1380,1304],{"class":84},[70,1382,1383],{"class":189},"t",[70,1385,101],{"class":84},[70,1387,1388],{"class":189},"idx",[70,1390,1315],{"class":84},[70,1392,1318],{"class":76},[70,1394,1395],{"class":84}," idx ",[70,1397,1398],{"class":76},"===",[70,1400,1401],{"class":84}," i ",[70,1403,1404],{"class":76},"?",[70,1406,1407],{"class":84}," e.target.value ",[70,1409,59],{"class":76},[70,1411,1412],{"class":84}," t)\n",[70,1414,1415],{"class":72,"line":619},[70,1416,1417],{"class":84},"          )}\n",[70,1419,1420],{"class":72,"line":643},[70,1421,1422],{"class":84},"        \u002F>\n",[70,1424,1425],{"class":72,"line":653},[70,1426,1427],{"class":84},"      ))}\n",[70,1429,1430,1432,1434,1437,1439,1442,1444,1446,1448,1450,1452,1454,1457,1460,1462,1465,1467],{"class":72,"line":658},[70,1431,596],{"class":84},[70,1433,624],{"class":148},[70,1435,1436],{"class":80}," onClick",[70,1438,110],{"class":76},[70,1440,1441],{"class":84},"{() ",[70,1443,1318],{"class":76},[70,1445,1363],{"class":80},[70,1447,116],{"class":84},[70,1449,1383],{"class":189},[70,1451,193],{"class":76},[70,1453,94],{"class":84},[70,1455,1456],{"class":76},"...",[70,1458,1459],{"class":84},"t, ",[70,1461,120],{"class":119},[70,1463,1464],{"class":84},"])}>+ Add\u003C\u002F",[70,1466,624],{"class":148},[70,1468,640],{"class":84},[70,1470,1472],{"class":72,"line":1471},16,[70,1473,1474],{"class":84},"    \u003C\u002F>\n",[70,1476,1478],{"class":72,"line":1477},17,[70,1479,214],{"class":84},[70,1481,1483],{"class":72,"line":1482},18,[70,1484,220],{"class":84},[15,1486,1487],{},"With uncontrolled inputs you'd need to track refs in an array and manually\nread each one — significantly more code for the same behaviour.",[10,1489,1491],{"id":1490},"how-react-hook-form-bridges-the-gap","How React Hook Form bridges the gap",[15,1493,1494,1495,1498],{},"React Hook Form uses ",[19,1496,1497],{},"uncontrolled inputs internally"," but gives you a\ncontrolled-like developer experience. It subscribes to DOM change events\ndirectly without storing every keystroke in React state, which means far fewer\nre-renders:",[61,1500,1502],{"className":63,"code":1501,"language":65,"meta":66,"style":66},"import { useForm } from 'react-hook-form'\n\nfunction SignupForm() {\n  const {\n    register,\n    handleSubmit,\n    formState: { errors }\n  } = useForm()\n\n  return (\n    \u003Cform onSubmit={handleSubmit(data => api.signup(data))}>\n      \u003Cinput\n        {...register('email', {\n          required: 'Email is required',\n          pattern: { value: \u002F@\u002F, message: 'Invalid email' }\n        })}\n      \u002F>\n      {errors.email && \u003Cp>{errors.email.message}\u003C\u002Fp>}\n      \u003Cbutton type=\"submit\">Sign up\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n",[33,1503,1504,1518,1522,1531,1538,1546,1553,1566,1578,1582,1588,1619,1625,1643,1653,1676,1681,1686,1706,1726,1735,1740],{"__ignoreMap":66},[70,1505,1506,1509,1512,1515],{"class":72,"line":73},[70,1507,1508],{"class":76},"import",[70,1510,1511],{"class":84}," { useForm } ",[70,1513,1514],{"class":76},"from",[70,1516,1517],{"class":119}," 'react-hook-form'\n",[70,1519,1520],{"class":72,"line":88},[70,1521,130],{"emptyLinePlaceholder":129},[70,1523,1524,1526,1529],{"class":72,"line":126},[70,1525,77],{"class":76},[70,1527,1528],{"class":80}," SignupForm",[70,1530,85],{"class":84},[70,1532,1533,1535],{"class":72,"line":133},[70,1534,91],{"class":76},[70,1536,1537],{"class":84}," {\n",[70,1539,1540,1543],{"class":72,"line":142},[70,1541,1542],{"class":97},"    register",[70,1544,1545],{"class":84},",\n",[70,1547,1548,1551],{"class":72,"line":152},[70,1549,1550],{"class":97},"    handleSubmit",[70,1552,1545],{"class":84},[70,1554,1555,1558,1561,1564],{"class":72,"line":163},[70,1556,1557],{"class":189},"    formState",[70,1559,1560],{"class":84},": { ",[70,1562,1563],{"class":97},"errors",[70,1565,1129],{"class":84},[70,1567,1568,1571,1573,1576],{"class":72,"line":178},[70,1569,1570],{"class":84},"  } ",[70,1572,110],{"class":76},[70,1574,1575],{"class":80}," useForm",[70,1577,550],{"class":84},[70,1579,1580],{"class":72,"line":205},[70,1581,130],{"emptyLinePlaceholder":129},[70,1583,1584,1586],{"class":72,"line":211},[70,1585,136],{"class":76},[70,1587,139],{"class":84},[70,1589,1590,1592,1594,1596,1598,1600,1603,1605,1608,1610,1613,1616],{"class":72,"line":217},[70,1591,145],{"class":84},[70,1593,583],{"class":148},[70,1595,586],{"class":80},[70,1597,110],{"class":76},[70,1599,186],{"class":84},[70,1601,1602],{"class":80},"handleSubmit",[70,1604,116],{"class":84},[70,1606,1607],{"class":189},"data",[70,1609,193],{"class":76},[70,1611,1612],{"class":84}," api.",[70,1614,1615],{"class":80},"signup",[70,1617,1618],{"class":84},"(data))}>\n",[70,1620,1621,1623],{"class":72,"line":619},[70,1622,596],{"class":84},[70,1624,149],{"class":148},[70,1626,1627,1630,1632,1635,1637,1640],{"class":72,"line":643},[70,1628,1629],{"class":84},"        {",[70,1631,1456],{"class":76},[70,1633,1634],{"class":80},"register",[70,1636,116],{"class":84},[70,1638,1639],{"class":119},"'email'",[70,1641,1642],{"class":84},", {\n",[70,1644,1645,1648,1651],{"class":72,"line":653},[70,1646,1647],{"class":84},"          required: ",[70,1649,1650],{"class":119},"'Email is required'",[70,1652,1545],{"class":84},[70,1654,1655,1658,1661,1665,1668,1671,1674],{"class":72,"line":658},[70,1656,1657],{"class":84},"          pattern: { value:",[70,1659,1660],{"class":119}," \u002F",[70,1662,1664],{"class":1663},"sA_wV","@",[70,1666,1667],{"class":119},"\u002F",[70,1669,1670],{"class":84},", message: ",[70,1672,1673],{"class":119},"'Invalid email'",[70,1675,1129],{"class":84},[70,1677,1678],{"class":72,"line":1471},[70,1679,1680],{"class":84},"        })}\n",[70,1682,1683],{"class":72,"line":1477},[70,1684,1685],{"class":84},"      \u002F>\n",[70,1687,1688,1691,1694,1696,1698,1701,1703],{"class":72,"line":1482},[70,1689,1690],{"class":84},"      {errors.email ",[70,1692,1693],{"class":76},"&&",[70,1695,802],{"class":84},[70,1697,15],{"class":148},[70,1699,1700],{"class":84},">{errors.email.message}\u003C\u002F",[70,1702,15],{"class":148},[70,1704,1705],{"class":84},">}\n",[70,1707,1709,1711,1713,1715,1717,1719,1722,1724],{"class":72,"line":1708},19,[70,1710,596],{"class":84},[70,1712,624],{"class":148},[70,1714,627],{"class":80},[70,1716,110],{"class":76},[70,1718,632],{"class":119},[70,1720,1721],{"class":84},">Sign up\u003C\u002F",[70,1723,624],{"class":148},[70,1725,640],{"class":84},[70,1727,1729,1731,1733],{"class":72,"line":1728},20,[70,1730,646],{"class":84},[70,1732,583],{"class":148},[70,1734,640],{"class":84},[70,1736,1738],{"class":72,"line":1737},21,[70,1739,214],{"class":84},[70,1741,1743],{"class":72,"line":1742},22,[70,1744,220],{"class":84},[15,1746,1747,1749,1750,1752,1753,1755],{},[33,1748,1634],{}," wires up a ",[33,1751,486],{}," and an ",[33,1754,58],{}," handler internally — the\ncomponent re-renders only when validation state changes or on submit, not on\nevery keystroke.",[15,1757,1758,1759,395,1762,1765],{},"For controlled integration (when the input value must flow back to React\nstate), use ",[33,1760,1761],{},"Controller",[33,1763,1764],{},"useController",".",[10,1767,1769],{"id":1768},"quick-decision-guide","Quick decision guide",[833,1771,1772,1782],{},[836,1773,1774],{},[839,1775,1776,1779],{},[842,1777,1778],{},"Need",[842,1780,1781],{},"Prefer",[849,1783,1784,1792,1800,1808,1815,1823],{},[839,1785,1786,1789],{},[854,1787,1788],{},"Live validation",[854,1790,1791],{},"Controlled",[839,1793,1794,1797],{},[854,1795,1796],{},"Submit-only, simple form",[854,1798,1799],{},"Uncontrolled",[839,1801,1802,1805],{},[854,1803,1804],{},"File upload",[854,1806,1807],{},"Uncontrolled (forced)",[839,1809,1810,1813],{},[854,1811,1812],{},"Programmatic reset\u002Fprefill",[854,1814,1791],{},[839,1816,1817,1820],{},[854,1818,1819],{},"Large complex form with performance concerns",[854,1821,1822],{},"React Hook Form (uncontrolled internally)",[839,1824,1825,1828],{},[854,1826,1827],{},"Integration with non-React DOM library",[854,1829,1799],{},[10,1831,1833],{"id":1832},"key-interview-points","Key interview points",[23,1835,1836,1846,1854,1862,1876,1881],{},[26,1837,1838,1840,1841,249,1843,1845],{},[19,1839,1791],{},": React state owns the value via ",[33,1842,35],{},[33,1844,58],{},".\nFull power, some boilerplate.",[26,1847,1848,1850,1851,1853],{},[19,1849,1799],{},": DOM owns the value after mount. Read it with a ",[33,1852,486],{},".\nSimpler, less control.",[26,1855,1856,1858,1859,1861],{},[33,1857,35],{}," → controlled. ",[33,1860,609],{}," → uncontrolled seed.",[26,1863,1864,1865,1667,1867,1869,1870,1872,1873,1875],{},"Never pass ",[33,1866,394],{},[33,1868,398],{}," as the ",[33,1871,35],{}," prop — use ",[33,1874,120],{}," instead.",[26,1877,1878,1880],{},[33,1879,686],{}," is always uncontrolled.",[26,1882,1883],{},"React Hook Form uses uncontrolled internals for performance but controlled\nergonomics in the API.",[1885,1886,1887],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":66,"searchDepth":88,"depth":88,"links":1889},[1890,1891,1895,1898,1900,1901,1902,1903,1904],{"id":12,"depth":88,"text":13},{"id":48,"depth":88,"text":49,"children":1892},[1893,1894],{"id":256,"depth":126,"text":257},{"id":387,"depth":126,"text":388},{"id":479,"depth":88,"text":480,"children":1896},[1897],{"id":668,"depth":126,"text":669},{"id":825,"depth":88,"text":1899},"value vs defaultValue — the complete picture",{"id":1098,"depth":88,"text":1099},{"id":1231,"depth":88,"text":1232},{"id":1490,"depth":88,"text":1491},{"id":1768,"depth":88,"text":1769},{"id":1832,"depth":88,"text":1833},"A complete guide to controlled and uncontrolled components in React — value vs defaultValue, refs, when to use each, and how form libraries fit in.","medium","md","React","react",{"subtopicSlug":1911},"controlled-vs-uncontrolled","\u002Fblog\u002Freact-controlled-vs-uncontrolled-guide","\u002Freact\u002Fstate-and-data-flow\u002Fcontrolled-vs-uncontrolled",{"title":5,"description":1905},"blog\u002Freact-controlled-vs-uncontrolled-guide","Controlled vs Uncontrolled Components","State and Data Flow","state-and-data-flow","2026-06-24","K-uHpn48VBw_G-ySjKmJHL5QGjJ3vpez5luPHrjGE5I",1782244083220]