Repository: DevExpress/devextreme-react
Pull request #197 (2018-09-24 16:38:12)

Fix empty className issue


Patch:
diff --git a/src/core/component.ts b/src/core/component.ts
index 41b2807..ec4c13b 100644
--- a/src/core/component.ts
+++ b/src/core/component.ts
@@ -3,7 +3,7 @@ import * as React from "react";
 
 import OptionsManager from "./options-manager";
 import { findProps as findNestedTemplateProps, ITemplateMeta } from "./template";
-import { elementPropNames, separateProps } from "./widget-config";
+import { elementPropNames, getClassName, separateProps } from "./widget-config";
 
 import {
   getTemplateOptions,
@@ -146,20 +146,24 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
   }
 
   private _updateCssClasses(prevProps: P | null, newProps: P) {
-    const prevClasses = prevProps ? this._getCssClasses(prevProps) : null;
-    const newClasses = this._getCssClasses(newProps);
+    const prevClassName = prevProps ? getClassName(prevProps) : undefined;
+    const newClassName = getClassName(newProps);
 
-    if (!newClasses) { return; }
+    if (prevClassName === newClassName) { return; }
 
-    if (prevClasses) {
-      this._element.classList.remove(...prevClasses);
+    if (prevClassName) {
+      const classNames = prevClassName.split(" ").filter((c) => c);
+      if (classNames.length) {
+        this._element.classList.remove(...classNames);
+      }
     }
 
-    this._element.classList.add(...newClasses);
-  }
-
-  private _getCssClasses(props: Record<string, any>): string[] | null {
-    return separateProps(props, this._defaults, this._templateProps).classNames;
+    if (newClassName) {
+      const classNames = newClassName.split(" ").filter((c) => c);
+      if (classNames.length) {
+        this._element.classList.add(...classNames);
+      }
+    }
   }
 
   private _prepareProps(rawProps: Record<string, any>): IWidgetConfig {
diff --git a/src/core/widget-config.ts b/src/core/widget-config.ts
index 4273131..dee1890 100644
--- a/src/core/widget-config.ts
+++ b/src/core/widget-config.ts
@@ -1,6 +1,7 @@
 import { ITemplateMeta } from "./template";
 
 const elementPropNames = ["style", "id"];
+const classNamePropName = "className";
 
 function separateProps(
     props: Record<string, any>,
@@ -10,13 +11,11 @@ function separateProps(
     options: Record<string, any>;
     defaults: Record<string, any>;
     templates: Record<string, any>;
-    classNames: string[] | null;
 } {
     templateProps = templateProps || [];
     const defaults: Record<string, any> = {};
     const options: Record<string, any> = {};
     const templates: Record<string, any> = {};
-    let classNames: string[] | null = null;
 
     const knownTemplates: Record<string, any> = {};
 
@@ -28,13 +27,7 @@ function separateProps(
     Object.keys(props).forEach((key) => {
         const defaultOptionName = defaultsProps ? defaultsProps[key] : null;
 
-        if (isIgnoredProp(key)) {
-            if (key === "className") {
-                classNames = props[key].split(" ");
-            }
-
-            return;
-        }
+        if (isIgnoredProp(key)) { return; }
 
         if (defaultOptionName) {
             defaults[defaultOptionName] = props[key];
@@ -49,14 +42,19 @@ function separateProps(
         options[key] = props[key];
     });
 
-    return { options, defaults, templates, classNames };
+    return { options, defaults, templates };
+}
+
+function getClassName(props: Record<string, any>): string | undefined {
+    return props[classNamePropName];
 }
 
 function isIgnoredProp(name: string) {
-    return name === "children" || name === "className" || elementPropNames.indexOf(name) > -1;
+    return name === "children" || name === classNamePropName || elementPropNames.indexOf(name) > -1;
 }
 
 export {
     elementPropNames,
+    getClassName,
     separateProps
 };


Test patch:
diff --git a/src/core/__tests__/component.test.tsx b/src/core/__tests__/component.test.tsx
index f5f938b..4f8c1e1 100644
--- a/src/core/__tests__/component.test.tsx
+++ b/src/core/__tests__/component.test.tsx
@@ -91,6 +91,50 @@ describe("element attrs management", () => {
         expect((node as HTMLElement).style.background).toEqual("blue");
     });
 
+    it("sets id, className and style after init", () => {
+        const component = mount(
+            <TestComponent />
+        );
+
+        const node = component.getDOMNode();
+        component.setProps({
+            id: "id1",
+            className: "class1",
+            style: {
+                background: "red"
+            }
+        });
+
+        expect(node.id).toBe("id1");
+        expect(node.className).toBe("class1");
+        expect((node as HTMLElement).style.background).toEqual("red");
+    });
+
+    it("cleans className (empty string)", () => {
+        const component = mount(
+            <TestComponent className={"class1"} />
+        );
+
+        const node = component.getDOMNode();
+        component.setProps({
+            className: "",
+        });
+
+        expect(node.className).toBe("");
+    });
+
+    it("cleans className (undefined)", () => {
+        const component = mount(
+            <TestComponent className={"class1"} />
+        );
+
+        const node = component.getDOMNode();
+        component.setProps({
+            className: undefined,
+        });
+
+        expect(node.className).toBe("");
+    });
 });
 
 describe("disposing", () => {