77 * @flow
88 */
99
10- import type { InputWithWrapperState } from './ReactDOMInput' ;
11-
1210import {
1311 registrationNameDependencies ,
1412 possibleRegistrationNames ,
@@ -17,6 +15,7 @@ import {
1715import { canUseDOM } from 'shared/ExecutionEnvironment' ;
1816import { checkHtmlStringCoercion } from 'shared/CheckStringCoercion' ;
1917import { checkAttributeStringCoercion } from 'shared/CheckStringCoercion' ;
18+ import { checkControlledValueProps } from '../shared/ReactControlledValuePropTypes' ;
2019
2120import {
2221 getValueForAttribute ,
@@ -27,27 +26,24 @@ import {
2726 setValueForNamespacedAttribute ,
2827} from './DOMPropertyOperations' ;
2928import {
30- initWrapperState as ReactDOMInputInitWrapperState ,
31- postMountWrapper as ReactDOMInputPostMountWrapper ,
32- updateChecked as ReactDOMInputUpdateChecked ,
33- updateWrapper as ReactDOMInputUpdateWrapper ,
34- restoreControlledState as ReactDOMInputRestoreControlledState ,
29+ validateInputProps ,
30+ initInput ,
31+ updateInputChecked ,
32+ updateInput ,
33+ restoreControlledInputState ,
3534} from './ReactDOMInput' ;
35+ import { initOption , validateOptionProps } from './ReactDOMOption' ;
3636import {
37- postMountWrapper as ReactDOMOptionPostMountWrapper ,
38- validateProps as ReactDOMOptionValidateProps ,
39- } from './ReactDOMOption' ;
40- import {
41- initWrapperState as ReactDOMSelectInitWrapperState ,
42- postMountWrapper as ReactDOMSelectPostMountWrapper ,
43- restoreControlledState as ReactDOMSelectRestoreControlledState ,
44- postUpdateWrapper as ReactDOMSelectPostUpdateWrapper ,
37+ validateSelectProps ,
38+ initSelect ,
39+ restoreControlledSelectState ,
40+ updateSelect ,
4541} from './ReactDOMSelect' ;
4642import {
47- initWrapperState as ReactDOMTextareaInitWrapperState ,
48- postMountWrapper as ReactDOMTextareaPostMountWrapper ,
49- updateWrapper as ReactDOMTextareaUpdateWrapper ,
50- restoreControlledState as ReactDOMTextareaRestoreControlledState ,
43+ validateTextareaProps ,
44+ initTextarea ,
45+ updateTextarea ,
46+ restoreControlledTextareaState ,
5147} from './ReactDOMTextarea' ;
5248import { track } from './inputValueTracking' ;
5349import setInnerHTML from './setInnerHTML' ;
@@ -79,6 +75,8 @@ import {
7975 listenToNonDelegatedEvent ,
8076} from '../events/DOMPluginEventSystem' ;
8177
78+ let didWarnControlledToUncontrolled = false ;
79+ let didWarnUncontrolledToControlled = false ;
8280let didWarnInvalidHydration = false ;
8381let canDiffStyleForHydrationWarning ;
8482if ( __DEV__ ) {
@@ -805,7 +803,9 @@ export function setInitialProperties(
805803 break ;
806804 }
807805 case 'input' : {
808- ReactDOMInputInitWrapperState ( domElement , props ) ;
806+ if ( __DEV__ ) {
807+ checkControlledValueProps ( 'input' , props ) ;
808+ }
809809 // We listen to this event in case to ensure emulated bubble
810810 // listeners still fire for the invalid event.
811811 listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
@@ -834,10 +834,10 @@ export function setInitialProperties(
834834 break ;
835835 }
836836 case 'checked' : {
837- const node = ( ( domElement : any ) : InputWithWrapperState ) ;
838837 const checked =
839- propValue != null ? propValue : node . _wrapperState . initialChecked ;
840- node . checked =
838+ propValue != null ? propValue : props . defaultChecked ;
839+ const inputElement : HTMLInputElement = ( domElement : any ) ;
840+ inputElement . checked =
841841 ! ! checked &&
842842 typeof checked !== 'function' &&
843843 checked !== 'symbol' ;
@@ -866,11 +866,14 @@ export function setInitialProperties(
866866 // TODO: Make sure we check if this is still unmounted or do any clean
867867 // up necessary since we never stop tracking anymore.
868868 track ( ( domElement : any ) ) ;
869- ReactDOMInputPostMountWrapper ( domElement , props , false ) ;
869+ validateInputProps ( domElement , props ) ;
870+ initInput ( domElement , props , false ) ;
870871 return ;
871872 }
872873 case 'select' : {
873- ReactDOMSelectInitWrapperState ( domElement , props ) ;
874+ if ( __DEV__ ) {
875+ checkControlledValueProps ( 'select' , props ) ;
876+ }
874877 // We listen to this event in case to ensure emulated bubble
875878 // listeners still fire for the invalid event.
876879 listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
@@ -893,11 +896,14 @@ export function setInitialProperties(
893896 }
894897 }
895898 }
896- ReactDOMSelectPostMountWrapper ( domElement , props ) ;
899+ validateSelectProps ( domElement , props ) ;
900+ initSelect ( domElement , props ) ;
897901 return ;
898902 }
899903 case 'textarea' : {
900- ReactDOMTextareaInitWrapperState ( domElement , props ) ;
904+ if ( __DEV__ ) {
905+ checkControlledValueProps ( 'textarea' , props ) ;
906+ }
901907 // We listen to this event in case to ensure emulated bubble
902908 // listeners still fire for the invalid event.
903909 listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
@@ -936,11 +942,12 @@ export function setInitialProperties(
936942 // TODO: Make sure we check if this is still unmounted or do any clean
937943 // up necessary since we never stop tracking anymore.
938944 track ( ( domElement : any ) ) ;
939- ReactDOMTextareaPostMountWrapper ( domElement , props ) ;
945+ validateTextareaProps ( domElement , props ) ;
946+ initTextarea ( domElement , props ) ;
940947 return ;
941948 }
942949 case 'option' : {
943- ReactDOMOptionValidateProps ( domElement , props ) ;
950+ validateOptionProps ( domElement , props ) ;
944951 for ( const propKey in props ) {
945952 if ( ! props . hasOwnProperty ( propKey ) ) {
946953 continue ;
@@ -963,7 +970,7 @@ export function setInitialProperties(
963970 }
964971 }
965972 }
966- ReactDOMOptionPostMountWrapper ( domElement , props ) ;
973+ initOption ( domElement , props ) ;
967974 return ;
968975 }
969976 case 'dialog' : {
@@ -1213,17 +1220,17 @@ export function updateProperties(
12131220 // In the middle of an update, it is possible to have multiple checked.
12141221 // When a checked radio tries to change name, browser makes another radio's checked false.
12151222 if ( nextProps . type === 'radio' && nextProps . name != null ) {
1216- ReactDOMInputUpdateChecked ( domElement , nextProps ) ;
1223+ updateInputChecked ( domElement , nextProps ) ;
12171224 }
12181225 for ( let i = 0 ; i < updatePayload . length ; i += 2 ) {
12191226 const propKey = updatePayload [ i ] ;
12201227 const propValue = updatePayload [ i + 1 ] ;
12211228 switch ( propKey ) {
12221229 case 'checked' : {
1223- const node = ( ( domElement : any ) : InputWithWrapperState ) ;
12241230 const checked =
1225- propValue != null ? propValue : node . _wrapperState . initialChecked ;
1226- node . checked =
1231+ propValue != null ? propValue : nextProps . defaultChecked ;
1232+ const inputElement : HTMLInputElement = ( domElement : any ) ;
1233+ inputElement . checked =
12271234 ! ! checked &&
12281235 typeof checked !== 'function' &&
12291236 checked !== 'symbol' ;
@@ -1249,10 +1256,50 @@ export function updateProperties(
12491256 }
12501257 }
12511258 }
1259+
1260+ if ( __DEV__ ) {
1261+ const wasControlled =
1262+ lastProps . type === 'checkbox' || lastProps . type === 'radio'
1263+ ? lastProps . checked != null
1264+ : lastProps . value != null ;
1265+ const isControlled =
1266+ nextProps . type === 'checkbox' || nextProps . type === 'radio'
1267+ ? nextProps . checked != null
1268+ : nextProps . value != null ;
1269+
1270+ if (
1271+ ! wasControlled &&
1272+ isControlled &&
1273+ ! didWarnUncontrolledToControlled
1274+ ) {
1275+ console . error (
1276+ 'A component is changing an uncontrolled input to be controlled. ' +
1277+ 'This is likely caused by the value changing from undefined to ' +
1278+ 'a defined value, which should not happen. ' +
1279+ 'Decide between using a controlled or uncontrolled input ' +
1280+ 'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components' ,
1281+ ) ;
1282+ didWarnUncontrolledToControlled = true ;
1283+ }
1284+ if (
1285+ wasControlled &&
1286+ ! isControlled &&
1287+ ! didWarnControlledToUncontrolled
1288+ ) {
1289+ console . error (
1290+ 'A component is changing a controlled input to be uncontrolled. ' +
1291+ 'This is likely caused by the value changing from a defined to ' +
1292+ 'undefined, which should not happen. ' +
1293+ 'Decide between using a controlled or uncontrolled input ' +
1294+ 'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components' ,
1295+ ) ;
1296+ didWarnControlledToUncontrolled = true ;
1297+ }
1298+ }
12521299 // Update the wrapper around inputs *after* updating props. This has to
12531300 // happen after updating the rest of props. Otherwise HTML5 input validations
12541301 // raise warnings and prevent the new value from being assigned.
1255- ReactDOMInputUpdateWrapper ( domElement , nextProps ) ;
1302+ updateInput ( domElement , nextProps ) ;
12561303 return ;
12571304 }
12581305 case 'select' : {
@@ -1272,7 +1319,7 @@ export function updateProperties(
12721319 }
12731320 // <select> value update needs to occur after <option> children
12741321 // reconciliation
1275- ReactDOMSelectPostUpdateWrapper ( domElement , nextProps ) ;
1322+ updateSelect ( domElement , lastProps , nextProps ) ;
12761323 return ;
12771324 }
12781325 case 'textarea' : {
@@ -1303,7 +1350,7 @@ export function updateProperties(
13031350 }
13041351 }
13051352 }
1306- ReactDOMTextareaUpdateWrapper ( domElement , nextProps ) ;
1353+ updateTextarea ( domElement , nextProps ) ;
13071354 return ;
13081355 }
13091356 case 'option' : {
@@ -2263,38 +2310,47 @@ export function diffHydratedProperties(
22632310 listenToNonDelegatedEvent ( 'toggle' , domElement ) ;
22642311 break ;
22652312 case 'input' :
2266- ReactDOMInputInitWrapperState ( domElement , props ) ;
2313+ if ( __DEV__ ) {
2314+ checkControlledValueProps ( 'input' , props ) ;
2315+ }
22672316 // We listen to this event in case to ensure emulated bubble
22682317 // listeners still fire for the invalid event.
22692318 listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
22702319 // TODO: Make sure we check if this is still unmounted or do any clean
22712320 // up necessary since we never stop tracking anymore.
22722321 track ( ( domElement : any ) ) ;
2322+ validateInputProps ( domElement , props ) ;
22732323 // For input and textarea we current always set the value property at
22742324 // post mount to force it to diverge from attributes. However, for
22752325 // option and select we don't quite do the same thing and select
22762326 // is not resilient to the DOM state changing so we don't do that here.
22772327 // TODO: Consider not doing this for input and textarea.
2278- ReactDOMInputPostMountWrapper ( domElement , props , true ) ;
2328+ initInput ( domElement , props , true ) ;
22792329 break ;
22802330 case 'option' :
2281- ReactDOMOptionValidateProps ( domElement , props ) ;
2331+ validateOptionProps ( domElement , props ) ;
22822332 break ;
22832333 case 'select' :
2284- ReactDOMSelectInitWrapperState ( domElement , props ) ;
2334+ if ( __DEV__ ) {
2335+ checkControlledValueProps ( 'select' , props ) ;
2336+ }
22852337 // We listen to this event in case to ensure emulated bubble
22862338 // listeners still fire for the invalid event.
22872339 listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
2340+ validateSelectProps ( domElement , props ) ;
22882341 break ;
22892342 case 'textarea' :
2290- ReactDOMTextareaInitWrapperState ( domElement , props ) ;
2343+ if ( __DEV__ ) {
2344+ checkControlledValueProps ( 'textarea' , props ) ;
2345+ }
22912346 // We listen to this event in case to ensure emulated bubble
22922347 // listeners still fire for the invalid event.
22932348 listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
22942349 // TODO: Make sure we check if this is still unmounted or do any clean
22952350 // up necessary since we never stop tracking anymore.
22962351 track ( ( domElement : any ) ) ;
2297- ReactDOMTextareaPostMountWrapper ( domElement , props ) ;
2352+ validateTextareaProps ( domElement , props ) ;
2353+ initTextarea ( domElement , props ) ;
22982354 break ;
22992355 }
23002356
@@ -2472,13 +2528,13 @@ export function restoreControlledState(
24722528) : void {
24732529 switch ( tag ) {
24742530 case 'input ':
2475- ReactDOMInputRestoreControlledState ( domElement , props ) ;
2531+ restoreControlledInputState ( domElement , props ) ;
24762532 return ;
24772533 case 'textarea ':
2478- ReactDOMTextareaRestoreControlledState ( domElement , props ) ;
2534+ restoreControlledTextareaState ( domElement , props ) ;
24792535 return ;
24802536 case 'select ':
2481- ReactDOMSelectRestoreControlledState ( domElement , props ) ;
2537+ restoreControlledSelectState ( domElement , props ) ;
24822538 return ;
24832539 }
24842540}
0 commit comments