diff --git a/packages/react-dom-bindings/src/client/CSSPropertyOperations.js b/packages/react-dom-bindings/src/client/CSSPropertyOperations.js index 55fd43ec479d0..a7c66c98a0db8 100644 --- a/packages/react-dom-bindings/src/client/CSSPropertyOperations.js +++ b/packages/react-dom-bindings/src/client/CSSPropertyOperations.js @@ -254,3 +254,52 @@ function validateShorthandPropertyCollisionInDev(prevStyles, nextStyles) { } } } + +export function constructClassNameString(args) { + const convertToString = arg => { + if (typeof arg === 'string') { + return arg; + } + + if (typeof arg === 'number') { + return arg.toString(); + } + + if (typeof arg === 'object') { + if (Array.isArray(arg)) { + return arg + .reduce((acc, arg2) => { + if (arg2) { + const argString = convertToString(arg2); + if (argString) { + return [...acc, argString]; + } + } + return acc; + }, []) + .join(' '); + } + + return Object.entries(arg) + .filter(([key, value]) => value) + .map(([key]) => key) + .join(' '); + } + + return ''; + }; + + const className = args + .reduce((acc, arg) => { + if (arg) { + const argString = convertToString(arg); + if (argString) { + return [...acc, argString]; + } + } + return acc; + }, []) + .join(' '); + + return className; +} diff --git a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js index 867eeba4a0869..ffec02e8ccac6 100644 --- a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js +++ b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js @@ -11,6 +11,7 @@ import isAttributeNameSafe from '../shared/isAttributeNameSafe'; import {enableTrustedTypesIntegration} from 'shared/ReactFeatureFlags'; import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion'; import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree'; +import {constructClassNameString} from './CSSPropertyOperations'; /** * Get the value for a attribute on a node. Only used in DEV for SSR validation. @@ -157,6 +158,16 @@ export function setValueForKnownAttribute( ); } +export function setComputedValueForClassAttribute(node: Element, value: mixed | Array) { + if (value === null) { + node.removeAttribute('class'); + return; + } + + const computedValue = constructClassNameString(value); + setValueForKnownAttribute(node, 'class', computedValue); +} + export function setValueForNamespacedAttribute( node: Element, namespace: string, diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 673fd47eca464..4ea899fbab52e 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -28,6 +28,7 @@ import { setValueForKnownAttribute, setValueForAttribute, setValueForNamespacedAttribute, + setComputedValueForClassAttribute, } from './DOMPropertyOperations'; import { validateInputProps, @@ -388,6 +389,9 @@ function setProp( case 'className': setValueForKnownAttribute(domElement, 'class', value); break; + case 'classNames': + setComputedValueForClassAttribute(domElement, value); + break; case 'tabIndex': // This has to be case sensitive in SVG. setValueForKnownAttribute(domElement, 'tabindex', value);