Use strict equality (===) to prevent type coercion bugs
props.checked !== undefined &&
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910// TODO: direct imports like some-package/src/* are bad. Fix me.11import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';1213import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';14import {getToStringValue, toString} from './ToStringValue';15import {track, trackHydrated, updateValueIfChanged} from './inputValueTracking';16import getActiveElement from './getActiveElement';17import {18 disableInputAttributeSyncing,19 enableHydrationChangeEvent,20} from 'shared/ReactFeatureFlags';21import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';2223import type {ToStringValue} from './ToStringValue';24import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';25import {queueChangeEvent} from '../events/ReactDOMEventReplaying';2627let didWarnValueDefaultValue = false;28let didWarnCheckedDefaultChecked = false;2930/**31 * Implements an <input> host component that allows setting these optional32 * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.33 *34 * If `checked` or `value` are not supplied (or null/undefined), user actions35 * that affect the checked state or value will trigger updates to the element.36 *37 * If they are supplied (and not null/undefined), the rendered element will not38 * trigger updates to the element. Instead, the props must change in order for39 * the rendered element to be updated.40 *41 * The rendered element will be initialized as unchecked (or `defaultChecked`)42 * with an empty value (or `defaultValue`).43 *44 * See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html45 */4647export function validateInputProps(element: Element, props: Object) {48 if (__DEV__) {49 // Normally we check for undefined and null the same, but explicitly specifying both50 // properties, at all is probably worth warning for. We could move this either direction51 // and just make it ok to pass null or just check hasOwnProperty.52 if (53 props.checked !== undefined &&54 props.defaultChecked !== undefined &&55 !didWarnCheckedDefaultChecked56 ) {57 console.error(58 '%s contains an input of type %s with both checked and defaultChecked props. ' +59 'Input elements must be either controlled or uncontrolled ' +60 '(specify either the checked prop, or the defaultChecked prop, but not ' +61 'both). Decide between using a controlled or uncontrolled input ' +62 'element and remove one of these props. More info: ' +63 'https://react.dev/link/controlled-components',64 getCurrentFiberOwnerNameInDevOrNull() || 'A component',65 props.type,66 );67 didWarnCheckedDefaultChecked = true;68 }69 if (70 props.value !== undefined &&71 props.defaultValue !== undefined &&72 !didWarnValueDefaultValue73 ) {74 console.error(75 '%s contains an input of type %s with both value and defaultValue props. ' +76 'Input elements must be either controlled or uncontrolled ' +77 '(specify either the value prop, or the defaultValue prop, but not ' +78 'both). Decide between using a controlled or uncontrolled input ' +79 'element and remove one of these props. More info: ' +80 'https://react.dev/link/controlled-components',81 getCurrentFiberOwnerNameInDevOrNull() || 'A component',82 props.type,83 );84 didWarnValueDefaultValue = true;85 }86 }87}8889export function updateInput(90 element: Element,91 value: ?string,92 defaultValue: ?string,93 lastDefaultValue: ?string,94 checked: ?boolean,95 defaultChecked: ?boolean,96 type: ?string,97 name: ?string,98) {99 const node: HTMLInputElement = element as any;100101 // Temporarily disconnect the input from any radio buttons.102 // Changing the type or name as the same time as changing the checked value103 // needs to be atomically applied. We can only ensure that by disconnecting104 // the name while do the mutations and then reapply the name after that's done.105 node.name = '';106107 if (108 type != null &&109 typeof type !== 'function' &&110 typeof type !== 'symbol' &&111 typeof type !== 'boolean'112 ) {113 if (__DEV__) {114 checkAttributeStringCoercion(type, 'type');115 }116 node.type = type;117 } else {118 node.removeAttribute('type');119 }120121 if (value != null) {122 if (type === 'number') {123 if (124 // $FlowFixMe[incompatible-type]125 // $FlowFixMe[invalid-compare]126 (value === 0 && node.value === '') ||127 // We explicitly want to coerce to number here if possible.128 // eslint-disable-next-line129 node.value != (value as any)130 ) {131 node.value = toString(getToStringValue(value));132 }133 } else if (node.value !== toString(getToStringValue(value))) {134 node.value = toString(getToStringValue(value));135 }136 } else if (type === 'submit' || type === 'reset') {137 // Submit/reset inputs need the attribute removed completely to avoid138 // blank-text buttons.139 node.removeAttribute('value');140 }141142 if (disableInputAttributeSyncing) {143 // When not syncing the value attribute, React only assigns a new value144 // whenever the defaultValue React prop has changed. When not present,145 // React does nothing146 if (defaultValue != null) {147 setDefaultValue(node, type, getToStringValue(defaultValue));148 } else if (lastDefaultValue != null) {149 node.removeAttribute('value');150 }151 } else {152 // When syncing the value attribute, the value comes from a cascade of153 // properties:154 // 1. The value React property155 // 2. The defaultValue React property156 // 3. Otherwise there should be no change157 if (value != null) {158 setDefaultValue(node, type, getToStringValue(value));159 } else if (defaultValue != null) {160 setDefaultValue(node, type, getToStringValue(defaultValue));161 } else if (lastDefaultValue != null) {162 node.removeAttribute('value');163 }164 }165166 if (disableInputAttributeSyncing) {167 // When not syncing the checked attribute, the attribute is directly168 // controllable from the defaultValue React property. It needs to be169 // updated as new props come in.170 if (defaultChecked == null) {171 node.removeAttribute('checked');172 } else {173 node.defaultChecked = !!defaultChecked;174 }175 } else {176 // When syncing the checked attribute, it only changes when it needs177 // to be removed, such as transitioning from a checkbox into a text input178 if (checked == null && defaultChecked != null) {179 node.defaultChecked = !!defaultChecked;180 }181 }182183 if (checked != null) {184 // Important to set this even if it's not a change in order to update input185 // value tracking with radio buttons186 // TODO: Should really update input value tracking for the whole radio187 // button group in an effect or something (similar to #27024)188 node.checked =189 checked && typeof checked !== 'function' && typeof checked !== 'symbol';190 }191192 if (193 name != null &&194 typeof name !== 'function' &&195 typeof name !== 'symbol' &&196 typeof name !== 'boolean'197 ) {198 if (__DEV__) {199 checkAttributeStringCoercion(name, 'name');200 }201 node.name = toString(getToStringValue(name));202 } else {203 node.removeAttribute('name');204 }205}206207export function initInput(208 element: Element,209 value: ?string,210 defaultValue: ?string,211 checked: ?boolean,212 defaultChecked: ?boolean,213 type: ?string,214 name: ?string,215 isHydrating: boolean,216) {217 const node: HTMLInputElement = element as any;218219 if (220 type != null &&221 typeof type !== 'function' &&222 typeof type !== 'symbol' &&223 typeof type !== 'boolean'224 ) {225 if (__DEV__) {226 checkAttributeStringCoercion(type, 'type');227 }228 node.type = type;229 }230231 if (value != null || defaultValue != null) {232 const isButton = type === 'submit' || type === 'reset';233234 // Avoid setting value attribute on submit/reset inputs as it overrides the235 // default value provided by the browser. See: #12872236 if (isButton && (value === undefined || value === null)) {237 // We track the value just in case it changes type later on.238 track(element as any);239 return;240 }241242 const defaultValueStr =243 defaultValue != null ? toString(getToStringValue(defaultValue)) : '';244 const initialValue =245 value != null ? toString(getToStringValue(value)) : defaultValueStr;246247 // Do not assign value if it is already set. This prevents user text input248 // from being lost during SSR hydration.249 if (!isHydrating || enableHydrationChangeEvent) {250 if (disableInputAttributeSyncing) {251 // When not syncing the value attribute, the value property points252 // directly to the React prop. Only assign it if it exists.253 if (value != null) {254 // Always assign on buttons so that it is possible to assign an255 // empty string to clear button text.256 //257 // Otherwise, do not re-assign the value property if is empty. This258 // potentially avoids a DOM write and prevents Firefox (~60.0.1) from259 // prematurely marking required inputs as invalid. Equality is compared260 // to the current value in case the browser provided value is not an261 // empty string.262 if (isButton || toString(getToStringValue(value)) !== node.value) {263 node.value = toString(getToStringValue(value));264 }265 }266 } else {267 // When syncing the value attribute, the value property should use268 // the wrapperState._initialValue property. This uses:269 //270 // 1. The value React property when present271 // 2. The defaultValue React property when present272 // 3. An empty string273 if (initialValue !== node.value) {274 node.value = initialValue;275 }276 }277 }278279 if (disableInputAttributeSyncing) {280 // When not syncing the value attribute, assign the value attribute281 // directly from the defaultValue React property (when present)282 if (defaultValue != null) {283 node.defaultValue = defaultValueStr;284 }285 } else {286 // Otherwise, the value attribute is synchronized to the property,287 // so we assign defaultValue to the same thing as the value property288 // assignment step above.289 node.defaultValue = initialValue;290 }291 }292293 // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug294 // this is needed to work around a chrome bug where setting defaultChecked295 // will sometimes influence the value of checked (even after detachment).296 // Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416297 // We need to temporarily unset name to avoid disrupting radio button groups.298299 const checkedOrDefault = checked != null ? checked : defaultChecked;300 // TODO: This 'function' or 'symbol' check isn't replicated in other places301 // so this semantic is inconsistent.302 const initialChecked =303 typeof checkedOrDefault !== 'function' &&304 typeof checkedOrDefault !== 'symbol' &&305 !!checkedOrDefault;306307 if (isHydrating && !enableHydrationChangeEvent) {308 // Detach .checked from .defaultChecked but leave user input alone309 node.checked = node.checked;310 } else {311 node.checked = !!initialChecked;312 }313314 if (disableInputAttributeSyncing) {315 // Only assign the checked attribute if it is defined. This saves316 // a DOM write when controlling the checked attribute isn't needed317 // (text inputs, submit/reset)318 if (defaultChecked != null) {319 node.defaultChecked = !node.defaultChecked;320 node.defaultChecked = !!defaultChecked;321 }322 } else {323 // When syncing the checked attribute, both the checked property and324 // attribute are assigned at the same time using defaultChecked. This uses:325 //326 // 1. The checked React property when present327 // 2. The defaultChecked React property when present328 // 3. Otherwise, false329 node.defaultChecked = !node.defaultChecked;330 node.defaultChecked = !!initialChecked;331 }332333 // Name needs to be set at the end so that it applies atomically to connected radio buttons.334 if (335 name != null &&336 typeof name !== 'function' &&337 typeof name !== 'symbol' &&338 typeof name !== 'boolean'339 ) {340 if (__DEV__) {341 checkAttributeStringCoercion(name, 'name');342 }343 node.name = name;344 }345 track(element as any);346}347348export function hydrateInput(349 element: Element,350 value: ?string,351 defaultValue: ?string,352 checked: ?boolean,353 defaultChecked: ?boolean,354): void {355 const node: HTMLInputElement = element as any;356357 const defaultValueStr =358 defaultValue != null ? toString(getToStringValue(defaultValue)) : '';359 const initialValue =360 value != null ? toString(getToStringValue(value)) : defaultValueStr;361362 const checkedOrDefault = checked != null ? checked : defaultChecked;363 // TODO: This 'function' or 'symbol' check isn't replicated in other places364 // so this semantic is inconsistent.365 const initialChecked =366 typeof checkedOrDefault !== 'function' &&367 typeof checkedOrDefault !== 'symbol' &&368 !!checkedOrDefault;369370 // Detach .checked from .defaultChecked but leave user input alone371 node.checked = node.checked;372373 const changed = trackHydrated(node as any, initialValue, initialChecked);374 if (changed) {375 // If the current value is different, that suggests that the user376 // changed it before hydration. Queue a replay of the change event.377 // For radio buttons the change event only fires on the selected one.378 if (node.type !== 'radio' || node.checked) {379 queueChangeEvent(node);380 }381 }382}383384export function restoreControlledInputState(element: Element, props: Object) {385 const rootNode: HTMLInputElement = element as any;386 updateInput(387 rootNode,388 props.value,389 props.defaultValue,390 props.defaultValue,391 props.checked,392 props.defaultChecked,393 props.type,394 props.name,395 );396 const name = props.name;397 if (props.type === 'radio' && name != null) {398 let queryRoot: Element = rootNode;399400 while (queryRoot.parentNode) {401 queryRoot = queryRoot.parentNode as any as Element;402 }403404 // If `rootNode.form` was non-null, then we could try `form.elements`,405 // but that sometimes behaves strangely in IE8. We could also try using406 // `form.getElementsByName`, but that will only return direct children407 // and won't include inputs that use the HTML5 `form=` attribute. Since408 // the input might not even be in a form. It might not even be in the409 // document. Let's just use the local `querySelectorAll` to ensure we don't410 // miss anything.411 if (__DEV__) {412 checkAttributeStringCoercion(name, 'name');413 }414 const group = queryRoot.querySelectorAll(415 'input[name="' +416 escapeSelectorAttributeValueInsideDoubleQuotes('' + name) +417 '"][type="radio"]',418 );419420 for (let i = 0; i < group.length; i++) {421 const otherNode = group[i] as any as HTMLInputElement;422 if (otherNode === rootNode || otherNode.form !== rootNode.form) {423 continue;424 }425 // This will throw if radio buttons rendered by different copies of React426 // and the same name are rendered into the same form (same as #1939).427 // That's probably okay; we don't support it just as we don't support428 // mixing React radio buttons with non-React ones.429 const otherProps: any = getFiberCurrentPropsFromNode(otherNode);430431 if (!otherProps) {432 throw new Error(433 'ReactDOMInput: Mixing React and non-React radio inputs with the ' +434 'same `name` is not supported.',435 );436 }437438 // If this is a controlled radio button group, forcing the input that439 // was previously checked to update will cause it to be come re-checked440 // as appropriate.441 updateInput(442 otherNode,443 otherProps.value,444 otherProps.defaultValue,445 otherProps.defaultValue,446 otherProps.checked,447 otherProps.defaultChecked,448 otherProps.type,449 otherProps.name,450 );451 }452453 // If any updateInput() call set .checked to true, an input in this group454 // (often, `rootNode` itself) may have become unchecked455 for (let i = 0; i < group.length; i++) {456 const otherNode = group[i] as any as HTMLInputElement;457 if (otherNode.form !== rootNode.form) {458 continue;459 }460 updateValueIfChanged(otherNode);461 }462 }463}464465// In Chrome, assigning defaultValue to certain input types triggers input validation.466// For number inputs, the display value loses trailing decimal points. For email inputs,467// Chrome raises "The specified value <x> is not a valid email address".468//469// Here we check to see if the defaultValue has actually changed, avoiding these problems470// when the user is inputting text471//472// https://github.com/facebook/react/issues/7253473export function setDefaultValue(474 node: HTMLInputElement,475 type: ?string,476 value: ToStringValue,477) {478 if (479 // Focused number inputs synchronize on blur. See ChangeEventPlugin.js480 type !== 'number' ||481 getActiveElement(node.ownerDocument) !== node482 ) {483 if (node.defaultValue !== toString(value)) {484 node.defaultValue = toString(value);485 }486 }487}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.