7
7
* @flow
8
8
*/
9
9
10
- import type { InputWithWrapperState } from './ReactDOMInput' ;
11
-
12
10
import {
13
11
registrationNameDependencies ,
14
12
possibleRegistrationNames ,
@@ -17,6 +15,7 @@ import {
17
15
import { canUseDOM } from 'shared/ExecutionEnvironment' ;
18
16
import { checkHtmlStringCoercion } from 'shared/CheckStringCoercion' ;
19
17
import { checkAttributeStringCoercion } from 'shared/CheckStringCoercion' ;
18
+ import { checkControlledValueProps } from '../shared/ReactControlledValuePropTypes' ;
20
19
21
20
import {
22
21
getValueForAttribute ,
@@ -27,27 +26,24 @@ import {
27
26
setValueForNamespacedAttribute ,
28
27
} from './DOMPropertyOperations' ;
29
28
import {
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 ,
35
34
} from './ReactDOMInput' ;
35
+ import { initOption , validateOptionProps } from './ReactDOMOption' ;
36
36
import {
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 ,
45
41
} from './ReactDOMSelect' ;
46
42
import {
47
- initWrapperState as ReactDOMTextareaInitWrapperState ,
48
- postMountWrapper as ReactDOMTextareaPostMountWrapper ,
49
- updateWrapper as ReactDOMTextareaUpdateWrapper ,
50
- restoreControlledState as ReactDOMTextareaRestoreControlledState ,
43
+ validateTextareaProps ,
44
+ initTextarea ,
45
+ updateTextarea ,
46
+ restoreControlledTextareaState ,
51
47
} from './ReactDOMTextarea' ;
52
48
import { track } from './inputValueTracking' ;
53
49
import setInnerHTML from './setInnerHTML' ;
@@ -79,6 +75,8 @@ import {
79
75
listenToNonDelegatedEvent ,
80
76
} from '../events/DOMPluginEventSystem' ;
81
77
78
+ let didWarnControlledToUncontrolled = false ;
79
+ let didWarnUncontrolledToControlled = false ;
82
80
let didWarnInvalidHydration = false ;
83
81
let canDiffStyleForHydrationWarning ;
84
82
if ( __DEV__ ) {
@@ -805,7 +803,9 @@ export function setInitialProperties(
805
803
break ;
806
804
}
807
805
case 'input' : {
808
- ReactDOMInputInitWrapperState ( domElement , props ) ;
806
+ if ( __DEV__ ) {
807
+ checkControlledValueProps ( 'input' , props ) ;
808
+ }
809
809
// We listen to this event in case to ensure emulated bubble
810
810
// listeners still fire for the invalid event.
811
811
listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
@@ -834,10 +834,10 @@ export function setInitialProperties(
834
834
break ;
835
835
}
836
836
case 'checked' : {
837
- const node = ( ( domElement : any ) : InputWithWrapperState ) ;
838
837
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 =
841
841
! ! checked &&
842
842
typeof checked !== 'function' &&
843
843
checked !== 'symbol' ;
@@ -866,11 +866,14 @@ export function setInitialProperties(
866
866
// TODO: Make sure we check if this is still unmounted or do any clean
867
867
// up necessary since we never stop tracking anymore.
868
868
track ( ( domElement : any ) ) ;
869
- ReactDOMInputPostMountWrapper ( domElement , props , false ) ;
869
+ validateInputProps ( domElement , props ) ;
870
+ initInput ( domElement , props , false ) ;
870
871
return ;
871
872
}
872
873
case 'select' : {
873
- ReactDOMSelectInitWrapperState ( domElement , props ) ;
874
+ if ( __DEV__ ) {
875
+ checkControlledValueProps ( 'select' , props ) ;
876
+ }
874
877
// We listen to this event in case to ensure emulated bubble
875
878
// listeners still fire for the invalid event.
876
879
listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
@@ -893,11 +896,14 @@ export function setInitialProperties(
893
896
}
894
897
}
895
898
}
896
- ReactDOMSelectPostMountWrapper ( domElement , props ) ;
899
+ validateSelectProps ( domElement , props ) ;
900
+ initSelect ( domElement , props ) ;
897
901
return ;
898
902
}
899
903
case 'textarea' : {
900
- ReactDOMTextareaInitWrapperState ( domElement , props ) ;
904
+ if ( __DEV__ ) {
905
+ checkControlledValueProps ( 'textarea' , props ) ;
906
+ }
901
907
// We listen to this event in case to ensure emulated bubble
902
908
// listeners still fire for the invalid event.
903
909
listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
@@ -936,11 +942,12 @@ export function setInitialProperties(
936
942
// TODO: Make sure we check if this is still unmounted or do any clean
937
943
// up necessary since we never stop tracking anymore.
938
944
track ( ( domElement : any ) ) ;
939
- ReactDOMTextareaPostMountWrapper ( domElement , props ) ;
945
+ validateTextareaProps ( domElement , props ) ;
946
+ initTextarea ( domElement , props ) ;
940
947
return ;
941
948
}
942
949
case 'option' : {
943
- ReactDOMOptionValidateProps ( domElement , props ) ;
950
+ validateOptionProps ( domElement , props ) ;
944
951
for ( const propKey in props ) {
945
952
if ( ! props . hasOwnProperty ( propKey ) ) {
946
953
continue ;
@@ -963,7 +970,7 @@ export function setInitialProperties(
963
970
}
964
971
}
965
972
}
966
- ReactDOMOptionPostMountWrapper ( domElement , props ) ;
973
+ initOption ( domElement , props ) ;
967
974
return ;
968
975
}
969
976
case 'dialog' : {
@@ -1213,17 +1220,17 @@ export function updateProperties(
1213
1220
// In the middle of an update, it is possible to have multiple checked.
1214
1221
// When a checked radio tries to change name, browser makes another radio's checked false.
1215
1222
if ( nextProps . type === 'radio' && nextProps . name != null ) {
1216
- ReactDOMInputUpdateChecked ( domElement , nextProps ) ;
1223
+ updateInputChecked ( domElement , nextProps ) ;
1217
1224
}
1218
1225
for ( let i = 0 ; i < updatePayload . length ; i += 2 ) {
1219
1226
const propKey = updatePayload [ i ] ;
1220
1227
const propValue = updatePayload [ i + 1 ] ;
1221
1228
switch ( propKey ) {
1222
1229
case 'checked' : {
1223
- const node = ( ( domElement : any ) : InputWithWrapperState ) ;
1224
1230
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 =
1227
1234
! ! checked &&
1228
1235
typeof checked !== 'function' &&
1229
1236
checked !== 'symbol' ;
@@ -1249,10 +1256,50 @@ export function updateProperties(
1249
1256
}
1250
1257
}
1251
1258
}
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
+ }
1252
1299
// Update the wrapper around inputs *after* updating props. This has to
1253
1300
// happen after updating the rest of props. Otherwise HTML5 input validations
1254
1301
// raise warnings and prevent the new value from being assigned.
1255
- ReactDOMInputUpdateWrapper ( domElement , nextProps ) ;
1302
+ updateInput ( domElement , nextProps ) ;
1256
1303
return ;
1257
1304
}
1258
1305
case 'select' : {
@@ -1272,7 +1319,7 @@ export function updateProperties(
1272
1319
}
1273
1320
// <select> value update needs to occur after <option> children
1274
1321
// reconciliation
1275
- ReactDOMSelectPostUpdateWrapper ( domElement , nextProps ) ;
1322
+ updateSelect ( domElement , lastProps , nextProps ) ;
1276
1323
return ;
1277
1324
}
1278
1325
case 'textarea' : {
@@ -1303,7 +1350,7 @@ export function updateProperties(
1303
1350
}
1304
1351
}
1305
1352
}
1306
- ReactDOMTextareaUpdateWrapper ( domElement , nextProps ) ;
1353
+ updateTextarea ( domElement , nextProps ) ;
1307
1354
return ;
1308
1355
}
1309
1356
case 'option' : {
@@ -2263,38 +2310,47 @@ export function diffHydratedProperties(
2263
2310
listenToNonDelegatedEvent ( 'toggle' , domElement ) ;
2264
2311
break ;
2265
2312
case 'input' :
2266
- ReactDOMInputInitWrapperState ( domElement , props ) ;
2313
+ if ( __DEV__ ) {
2314
+ checkControlledValueProps ( 'input' , props ) ;
2315
+ }
2267
2316
// We listen to this event in case to ensure emulated bubble
2268
2317
// listeners still fire for the invalid event.
2269
2318
listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
2270
2319
// TODO: Make sure we check if this is still unmounted or do any clean
2271
2320
// up necessary since we never stop tracking anymore.
2272
2321
track ( ( domElement : any ) ) ;
2322
+ validateInputProps ( domElement , props ) ;
2273
2323
// For input and textarea we current always set the value property at
2274
2324
// post mount to force it to diverge from attributes. However, for
2275
2325
// option and select we don't quite do the same thing and select
2276
2326
// is not resilient to the DOM state changing so we don't do that here.
2277
2327
// TODO: Consider not doing this for input and textarea.
2278
- ReactDOMInputPostMountWrapper ( domElement , props , true ) ;
2328
+ initInput ( domElement , props , true ) ;
2279
2329
break ;
2280
2330
case 'option' :
2281
- ReactDOMOptionValidateProps ( domElement , props ) ;
2331
+ validateOptionProps ( domElement , props ) ;
2282
2332
break ;
2283
2333
case 'select' :
2284
- ReactDOMSelectInitWrapperState ( domElement , props ) ;
2334
+ if ( __DEV__ ) {
2335
+ checkControlledValueProps ( 'select' , props ) ;
2336
+ }
2285
2337
// We listen to this event in case to ensure emulated bubble
2286
2338
// listeners still fire for the invalid event.
2287
2339
listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
2340
+ validateSelectProps ( domElement , props ) ;
2288
2341
break ;
2289
2342
case 'textarea' :
2290
- ReactDOMTextareaInitWrapperState ( domElement , props ) ;
2343
+ if ( __DEV__ ) {
2344
+ checkControlledValueProps ( 'textarea' , props ) ;
2345
+ }
2291
2346
// We listen to this event in case to ensure emulated bubble
2292
2347
// listeners still fire for the invalid event.
2293
2348
listenToNonDelegatedEvent ( 'invalid' , domElement ) ;
2294
2349
// TODO: Make sure we check if this is still unmounted or do any clean
2295
2350
// up necessary since we never stop tracking anymore.
2296
2351
track ( ( domElement : any ) ) ;
2297
- ReactDOMTextareaPostMountWrapper ( domElement , props ) ;
2352
+ validateTextareaProps ( domElement , props ) ;
2353
+ initTextarea ( domElement , props ) ;
2298
2354
break ;
2299
2355
}
2300
2356
@@ -2472,13 +2528,13 @@ export function restoreControlledState(
2472
2528
) : void {
2473
2529
switch ( tag ) {
2474
2530
case 'input ':
2475
- ReactDOMInputRestoreControlledState ( domElement , props ) ;
2531
+ restoreControlledInputState ( domElement , props ) ;
2476
2532
return ;
2477
2533
case 'textarea ':
2478
- ReactDOMTextareaRestoreControlledState ( domElement , props ) ;
2534
+ restoreControlledTextareaState ( domElement , props ) ;
2479
2535
return ;
2480
2536
case 'select ':
2481
- ReactDOMSelectRestoreControlledState ( domElement , props ) ;
2537
+ restoreControlledSelectState ( domElement , props ) ;
2482
2538
return ;
2483
2539
}
2484
2540
}
0 commit comments