diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index db56aad9af3fd..587933653326b 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -7,6 +7,8 @@ * @flow */ +import type {InputWithWrapperState} from './ReactDOMInput'; + import { registrationNameDependencies, possibleRegistrationNames, @@ -25,7 +27,6 @@ import { } from './DOMPropertyOperations'; import { initWrapperState as ReactDOMInputInitWrapperState, - getHostProps as ReactDOMInputGetHostProps, postMountWrapper as ReactDOMInputPostMountWrapper, updateChecked as ReactDOMInputUpdateChecked, updateWrapper as ReactDOMInputUpdateWrapper, @@ -37,14 +38,12 @@ import { } from './ReactDOMOption'; import { initWrapperState as ReactDOMSelectInitWrapperState, - getHostProps as ReactDOMSelectGetHostProps, postMountWrapper as ReactDOMSelectPostMountWrapper, restoreControlledState as ReactDOMSelectRestoreControlledState, postUpdateWrapper as ReactDOMSelectPostUpdateWrapper, } from './ReactDOMSelect'; import { initWrapperState as ReactDOMTextareaInitWrapperState, - getHostProps as ReactDOMTextareaGetHostProps, postMountWrapper as ReactDOMTextareaPostMountWrapper, updateWrapper as ReactDOMTextareaUpdateWrapper, restoreControlledState as ReactDOMTextareaRestoreControlledState, @@ -369,6 +368,16 @@ function setProp( } break; } + case 'onClick': { + // TODO: This cast may not be sound for SVG, MathML or custom elements. + if (value != null) { + if (__DEV__ && typeof value !== 'function') { + warnForInvalidEventListener(key, value); + } + trapClickOnNonInteractiveElement(((domElement: any): HTMLElement)); + } + break; + } case 'suppressContentEditableWarning': case 'suppressHydrationWarning': case 'defaultValue': // Reserved @@ -539,124 +548,194 @@ export function createTextNode( export function setInitialProperties( domElement: Element, tag: string, - rawProps: Object, + props: Object, ): void { - const isCustomComponentTag = isCustomComponent(tag, rawProps); if (__DEV__) { - validatePropertiesInDevelopment(tag, rawProps); + validatePropertiesInDevelopment(tag, props); } // TODO: Make sure that we check isMounted before firing any of these events. - let props: Object; + switch (tag) { - case 'dialog': + case 'input': { + ReactDOMInputInitWrapperState(domElement, props); + // We listen to this event in case to ensure emulated bubble + // listeners still fire for the invalid event. + listenToNonDelegatedEvent('invalid', domElement); + for (const propKey in props) { + if (!props.hasOwnProperty(propKey)) { + continue; + } + const propValue = props[propKey]; + if (propValue == null) { + continue; + } + switch (propKey) { + case 'checked': { + const node = ((domElement: any): InputWithWrapperState); + const checked = + propValue != null ? propValue : node._wrapperState.initialChecked; + node.checked = + !!checked && + typeof checked !== 'function' && + checked !== 'symbol'; + break; + } + case 'value': { + // This is handled by updateWrapper below. + break; + } + case 'children': + case 'dangerouslySetInnerHTML': { + if (propValue != null) { + throw new Error( + `${tag} is a void element tag and must neither have \`children\` nor ` + + 'use `dangerouslySetInnerHTML`.', + ); + } + break; + } + // defaultChecked and defaultValue are ignored by setProp + default: { + setProp(domElement, tag, propKey, propValue, false, props); + } + } + } + // TODO: Make sure we check if this is still unmounted or do any clean + // up necessary since we never stop tracking anymore. + track((domElement: any)); + ReactDOMInputPostMountWrapper(domElement, props, false); + return; + } + case 'select': { + ReactDOMSelectInitWrapperState(domElement, props); + // We listen to this event in case to ensure emulated bubble + // listeners still fire for the invalid event. + listenToNonDelegatedEvent('invalid', domElement); + for (const propKey in props) { + if (!props.hasOwnProperty(propKey)) { + continue; + } + const propValue = props[propKey]; + if (propValue == null) { + continue; + } + switch (propKey) { + case 'value': { + // This is handled by updateWrapper below. + break; + } + // defaultValue are ignored by setProp + default: { + setProp(domElement, tag, propKey, propValue, false, props); + } + } + } + ReactDOMSelectPostMountWrapper(domElement, props); + return; + } + case 'textarea': { + ReactDOMTextareaInitWrapperState(domElement, props); + // We listen to this event in case to ensure emulated bubble + // listeners still fire for the invalid event. + listenToNonDelegatedEvent('invalid', domElement); + for (const propKey in props) { + if (!props.hasOwnProperty(propKey)) { + continue; + } + const propValue = props[propKey]; + if (propValue == null) { + continue; + } + switch (propKey) { + case 'value': { + // This is handled by updateWrapper below. + break; + } + case 'children': { + // TODO: Handled by initWrapperState above. + break; + } + case 'dangerouslySetInnerHTML': { + if (propValue != null) { + // TODO: Do we really need a special error message for this. It's also pretty blunt. + throw new Error( + '`dangerouslySetInnerHTML` does not make sense on