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 {17 disableInputAttributeSyncing,18 enableHydrationChangeEvent,19} from 'shared/ReactFeatureFlags';20import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';2122import type {ToStringValue} from './ToStringValue';23import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';24import {queueChangeEvent} from '../events/ReactDOMEventReplaying';2526let didWarnValueDefaultValue = false;27let didWarnCheckedDefaultChecked = false;2829/**30 * Implements an <input> host component that allows setting these optional31 * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.32 *33 * If `checked` or `value` are not supplied (or null/undefined), user actions34 * that affect the checked state or value will trigger updates to the element.35 *36 * If they are supplied (and not null/undefined), the rendered element will not37 * trigger updates to the element. Instead, the props must change in order for38 * the rendered element to be updated.39 *40 * The rendered element will be initialized as unchecked (or `defaultChecked`)41 * with an empty value (or `defaultValue`).42 *43 * See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html44 */4546export function validateInputProps(element: Element, props: Object) {47 if (__DEV__) {48 // Normally we check for undefined and null the same, but explicitly specifying both49 // properties, at all is probably worth warning for. We could move this either direction50 // and just make it ok to pass null or just check hasOwnProperty.51 if (52 props.checked !== undefined &&53 props.defaultChecked !== undefined &&54 !didWarnCheckedDefaultChecked55 ) {56 console.error(57 '%s contains an input of type %s with both checked and defaultChecked props. ' +58 'Input elements must be either controlled or uncontrolled ' +59 '(specify either the checked prop, or the defaultChecked prop, but not ' +60 'both). Decide between using a controlled or uncontrolled input ' +61 'element and remove one of these props. More info: ' +62 'https://react.dev/link/controlled-components',63 getCurrentFiberOwnerNameInDevOrNull() || 'A component',64 props.type,65 );66 didWarnCheckedDefaultChecked = true;67 }68 if (69 props.value !== undefined &&70 props.defaultValue !== undefined &&71 !didWarnValueDefaultValue72 ) {73 console.error(74 '%s contains an input of type %s with both value and defaultValue props. ' +75 'Input elements must be either controlled or uncontrolled ' +76 '(specify either the value prop, or the defaultValue prop, but not ' +77 'both). Decide between using a controlled or uncontrolled input ' +78 'element and remove one of these props. More info: ' +79 'https://react.dev/link/controlled-components',80 getCurrentFiberOwnerNameInDevOrNull() || 'A component',81 props.type,82 );83 didWarnValueDefaultValue = true;84 }85 }86}8788export function updateInput(89 element: Element,90 value: ?string,91 defaultValue: ?string,92 lastDefaultValue: ?string,93 checked: ?boolean,94 defaultChecked: ?boolean,95 type: ?string,96 name: ?string,97) {98 const node: HTMLInputElement = element as any;99100 // Temporarily disconnect the input from any radio buttons.101 // Changing the type or name as the same time as changing the checked value102 // needs to be atomically applied. We can only ensure that by disconnecting103 // the name while do the mutations and then reapply the name after that's done.104 node.name = '';105106 if (107 type != null &&108 typeof type !== 'function' &&109 typeof type !== 'symbol' &&110 typeof type !== 'boolean'111 ) {112 if (__DEV__) {113 checkAttributeStringCoercion(type, 'type');114 }115 node.type = type;116 } else {117 node.removeAttribute('type');118 }119120 if (value != null) {121 if (type === 'number') {122 if (123 // "" == 0, so a cleared field wouldn't otherwise be restored to 0.124 // $FlowFixMe[incompatible-type]125 // $FlowFixMe[invalid-compare]126 (value === 0 && node.value === '') ||127 // We explicitly want to coerce to number here if possible, so that128 // other spellings of the same number (e.g. "0.0" mid-edit) aren't129 // clobbered while the user types.130 // eslint-disable-next-line131 node.value != (value as any)132 ) {133 node.value = toString(getToStringValue(value));134 }135 } else if (node.value !== toString(getToStringValue(value))) {136 node.value = toString(getToStringValue(value));137 }138 } else if (type === 'submit' || type === 'reset') {139 // Submit/reset inputs need the attribute removed completely to avoid140 // blank-text buttons.141 node.removeAttribute('value');142 }143144 if (disableInputAttributeSyncing) {145 // When not syncing the value attribute, React only assigns a new value146 // whenever the defaultValue React prop has changed. When not present,147 // React does nothing148 if (defaultValue != null) {149 setDefaultValue(node, getToStringValue(defaultValue));150 } else if (lastDefaultValue != null) {151 node.removeAttribute('value');152 }153 } else {154 // When syncing the value attribute, the value comes from a cascade of155 // properties:156 // 1. The value React property157 // 2. The defaultValue React property158 // 3. Otherwise there should be no change159 if (value != null) {160 if (161 type === 'number' &&162 // We explicitly want to coerce to number here if possible.163 // eslint-disable-next-line164 node.value == (value as any)165 ) {166 // node.value may be a different spelling of the same number (e.g.167 // "0.0" for 0). Mirror what's displayed, like the value setter does.168 // Not redundant with the assignment above: browsers sanitize invalid169 // assigned values to "", in which case we sync the React value below.170 setDefaultValue(node, getToStringValue(node.value));171 } else {172 setDefaultValue(node, getToStringValue(value));173 }174 } else if (defaultValue != null) {175 setDefaultValue(node, getToStringValue(defaultValue));176 } else if (lastDefaultValue != null) {177 node.removeAttribute('value');178 }179 }180181 if (disableInputAttributeSyncing) {182 // When not syncing the checked attribute, the attribute is directly183 // controllable from the defaultValue React property. It needs to be184 // updated as new props come in.185 if (defaultChecked == null) {186 node.removeAttribute('checked');187 } else {188 node.defaultChecked = !!defaultChecked;189 }190 } else {191 // When syncing the checked attribute, it only changes when it needs192 // to be removed, such as transitioning from a checkbox into a text input193 if (checked == null && defaultChecked != null) {194 node.defaultChecked = !!defaultChecked;195 }196 }197198 if (checked != null) {199 // Important to set this even if it's not a change in order to update input200 // value tracking with radio buttons201 // TODO: Should really update input value tracking for the whole radio202 // button group in an effect or something (similar to #27024)203 node.checked =204 checked && typeof checked !== 'function' && typeof checked !== 'symbol';205 }206207 if (208 name != null &&209 typeof name !== 'function' &&210 typeof name !== 'symbol' &&211 typeof name !== 'boolean'212 ) {213 if (__DEV__) {214 checkAttributeStringCoercion(name, 'name');215 }216 node.name = toString(getToStringValue(name));217 } else {218 node.removeAttribute('name');219 }220}221222export function initInput(223 element: Element,224 value: ?string,225 defaultValue: ?string,226 checked: ?boolean,227 defaultChecked: ?boolean,228 type: ?string,229 name: ?string,230 isHydrating: boolean,231) {232 const node: HTMLInputElement = element as any;233234 if (235 type != null &&236 typeof type !== 'function' &&237 typeof type !== 'symbol' &&238 typeof type !== 'boolean'239 ) {240 if (__DEV__) {241 checkAttributeStringCoercion(type, 'type');242 }243 node.type = type;244 }245246 if (value != null || defaultValue != null) {247 const isButton = type === 'submit' || type === 'reset';248249 // Avoid setting value attribute on submit/reset inputs as it overrides the250 // default value provided by the browser. See: #12872251 if (isButton && (value === undefined || value === null)) {252 // We track the value just in case it changes type later on.253 track(element as any);254 return;255 }256257 const defaultValueStr =258 defaultValue != null ? toString(getToStringValue(defaultValue)) : '';259 const initialValue =260 value != null ? toString(getToStringValue(value)) : defaultValueStr;261262 // Do not assign value if it is already set. This prevents user text input263 // from being lost during SSR hydration.264 if (!isHydrating || enableHydrationChangeEvent) {265 if (disableInputAttributeSyncing) {266 // When not syncing the value attribute, the value property points267 // directly to the React prop. Only assign it if it exists.268 if (value != null) {269 // Always assign on buttons so that it is possible to assign an270 // empty string to clear button text.271 //272 // Otherwise, do not re-assign the value property if is empty. This273 // potentially avoids a DOM write and prevents Firefox (~60.0.1) from274 // prematurely marking required inputs as invalid. Equality is compared275 // to the current value in case the browser provided value is not an276 // empty string.277 if (isButton || toString(getToStringValue(value)) !== node.value) {278 node.value = toString(getToStringValue(value));279 }280 }281 } else {282 // When syncing the value attribute, the value property should use283 // the wrapperState._initialValue property. This uses:284 //285 // 1. The value React property when present286 // 2. The defaultValue React property when present287 // 3. An empty string288 if (initialValue !== node.value) {289 node.value = initialValue;290 }291 }292 }293294 if (disableInputAttributeSyncing) {295 // When not syncing the value attribute, assign the value attribute296 // directly from the defaultValue React property (when present)297 if (defaultValue != null) {298 node.defaultValue = defaultValueStr;299 }300 } else {301 // Otherwise, the value attribute is synchronized to the property,302 // so we assign defaultValue to the same thing as the value property303 // assignment step above.304 node.defaultValue = initialValue;305 }306 }307308 // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug309 // this is needed to work around a chrome bug where setting defaultChecked310 // will sometimes influence the value of checked (even after detachment).311 // Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416312 // We need to temporarily unset name to avoid disrupting radio button groups.313314 const checkedOrDefault = checked != null ? checked : defaultChecked;315 // TODO: This 'function' or 'symbol' check isn't replicated in other places316 // so this semantic is inconsistent.317 const initialChecked =318 typeof checkedOrDefault !== 'function' &&319 typeof checkedOrDefault !== 'symbol' &&320 !!checkedOrDefault;321322 if (isHydrating && !enableHydrationChangeEvent) {323 // Detach .checked from .defaultChecked but leave user input alone324 node.checked = node.checked;325 } else {326 node.checked = !!initialChecked;327 }328329 if (disableInputAttributeSyncing) {330 // Only assign the checked attribute if it is defined. This saves331 // a DOM write when controlling the checked attribute isn't needed332 // (text inputs, submit/reset)333 if (defaultChecked != null) {334 node.defaultChecked = !node.defaultChecked;335 node.defaultChecked = !!defaultChecked;336 }337 } else {338 // When syncing the checked attribute, both the checked property and339 // attribute are assigned at the same time using defaultChecked. This uses:340 //341 // 1. The checked React property when present342 // 2. The defaultChecked React property when present343 // 3. Otherwise, false344 node.defaultChecked = !node.defaultChecked;345 node.defaultChecked = !!initialChecked;346 }347348 // Name needs to be set at the end so that it applies atomically to connected radio buttons.349 if (350 name != null &&351 typeof name !== 'function' &&352 typeof name !== 'symbol' &&353 typeof name !== 'boolean'354 ) {355 if (__DEV__) {356 checkAttributeStringCoercion(name, 'name');357 }358 node.name = name;359 }360 track(element as any);361}362363export function hydrateInput(364 element: Element,365 value: ?string,366 defaultValue: ?string,367 checked: ?boolean,368 defaultChecked: ?boolean,369): void {370 const node: HTMLInputElement = element as any;371372 const defaultValueStr =373 defaultValue != null ? toString(getToStringValue(defaultValue)) : '';374 const initialValue =375 value != null ? toString(getToStringValue(value)) : defaultValueStr;376377 const checkedOrDefault = checked != null ? checked : defaultChecked;378 // TODO: This 'function' or 'symbol' check isn't replicated in other places379 // so this semantic is inconsistent.380 const initialChecked =381 typeof checkedOrDefault !== 'function' &&382 typeof checkedOrDefault !== 'symbol' &&383 !!checkedOrDefault;384385 // Detach .checked from .defaultChecked but leave user input alone386 node.checked = node.checked;387388 const changed = trackHydrated(node as any, initialValue, initialChecked);389 if (changed) {390 // If the current value is different, that suggests that the user391 // changed it before hydration. Queue a replay of the change event.392 // For radio buttons the change event only fires on the selected one.393 if (node.type !== 'radio' || node.checked) {394 queueChangeEvent(node);395 }396 }397}398399export function restoreControlledInputState(element: Element, props: Object) {400 const rootNode: HTMLInputElement = element as any;401 updateInput(402 rootNode,403 props.value,404 props.defaultValue,405 props.defaultValue,406 props.checked,407 props.defaultChecked,408 props.type,409 props.name,410 );411 const name = props.name;412 if (props.type === 'radio' && name != null) {413 let queryRoot: Element = rootNode;414415 while (queryRoot.parentNode) {416 queryRoot = queryRoot.parentNode as any as Element;417 }418419 // If `rootNode.form` was non-null, then we could try `form.elements`,420 // but that sometimes behaves strangely in IE8. We could also try using421 // `form.getElementsByName`, but that will only return direct children422 // and won't include inputs that use the HTML5 `form=` attribute. Since423 // the input might not even be in a form. It might not even be in the424 // document. Let's just use the local `querySelectorAll` to ensure we don't425 // miss anything.426 if (__DEV__) {427 checkAttributeStringCoercion(name, 'name');428 }429 const group = queryRoot.querySelectorAll(430 'input[name="' +431 escapeSelectorAttributeValueInsideDoubleQuotes('' + name) +432 '"][type="radio"]',433 );434435 for (let i = 0; i < group.length; i++) {436 const otherNode = group[i] as any as HTMLInputElement;437 if (otherNode === rootNode || otherNode.form !== rootNode.form) {438 continue;439 }440 // This will throw if radio buttons rendered by different copies of React441 // and the same name are rendered into the same form (same as #1939).442 // That's probably okay; we don't support it just as we don't support443 // mixing React radio buttons with non-React ones.444 const otherProps: any = getFiberCurrentPropsFromNode(otherNode);445446 if (!otherProps) {447 throw new Error(448 'ReactDOMInput: Mixing React and non-React radio inputs with the ' +449 'same `name` is not supported.',450 );451 }452453 // If this is a controlled radio button group, forcing the input that454 // was previously checked to update will cause it to be come re-checked455 // as appropriate.456 updateInput(457 otherNode,458 otherProps.value,459 otherProps.defaultValue,460 otherProps.defaultValue,461 otherProps.checked,462 otherProps.defaultChecked,463 otherProps.type,464 otherProps.name,465 );466 }467468 // If any updateInput() call set .checked to true, an input in this group469 // (often, `rootNode` itself) may have become unchecked470 for (let i = 0; i < group.length; i++) {471 const otherNode = group[i] as any as HTMLInputElement;472 if (otherNode.form !== rootNode.form) {473 continue;474 }475 updateValueIfChanged(otherNode);476 }477 }478}479480function setDefaultValue(node: HTMLInputElement, value: ToStringValue) {481 if (node.defaultValue !== toString(value)) {482 node.defaultValue = toString(value);483 }484}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.