Repository: DevExpress/devextreme-react (js)

Issue:
TagBox raises an exception when a tag is removed and the tagRender property is defined
### Issue Type
bug report
#### Steps to Reproduce
1) Create a TagBox and define a `tagRender` prop.
2) Select several items via UI.
3) Click on the clear button.
#### Current Behavior
Exception
#### Expected Behavior
No Exception
#### Demo
https://stackblitz.com/edit/devextreme-react-g59gln 
### Environment Details
- devextreme version: ^18.1.4
- devextreme-react version: 18.1.4-alpha.8
- react version: latest ^16.2.0

Pull request:
Fix exception on template removing
fix #162 

Patch:
diff --git a/example/editor-example.tsx b/example/editor-example.tsx
index 62c76dc..10e3a29 100644
--- a/example/editor-example.tsx
+++ b/example/editor-example.tsx
@@ -2,6 +2,7 @@ import * as React from "react";
 import Example from "./example-block";
 
 import { SelectBox } from "../src/ui/select-box";
+import { TagBox } from "../src/ui/tag-box";
 import { TextBox } from "../src/ui/text-box";
 
 function fieldRender() {
@@ -13,6 +14,8 @@ export default class extends React.Component<any, any> {
         return (
             <Example title="Editors" state={this.state}>
                 <SelectBox fieldRender={fieldRender} />
+                <br />
+                <TagBox dataSource={["1", "2", "3"]} showClearButton={true} tagRender={() => <div>test</div>} />
             </Example>
         );
     }
diff --git a/src/core/template-helper.ts b/src/core/template-helper.ts
index cefdcc7..2c971f9 100644
--- a/src/core/template-helper.ts
+++ b/src/core/template-helper.ts
@@ -83,10 +83,25 @@ function getTemplateOptions(meta: IIntegrationDescr): {
     };
 }
 
+function prepareContainer(container: Element): Element {
+    container = unwrapElement(container);
+
+    if (container.nodeName === "TABLE" ) {
+        return container;
+    }
+
+    const result = document.createElement("div");
+    result.classList.add("dx-template-wrapper");
+    container.appendChild(result);
+
+    return result;
+}
+
 function wrapTemplate(templateDescr: ITemplateDescr, stateUpdater: StateUpdater): IDxTemplate {
     return {
         render: (data: IDxTemplateData) => {
             const templateId = "__template_" + generateID();
+            const container = prepareContainer(data.container);
             const createWrapper = (nestedTemplates) => {
                 const propsGetter = templateDescr.isNested
                     ? (prop) => nestedTemplates[templateDescr.name][prop]
@@ -98,13 +113,14 @@ function wrapTemplate(templateDescr: ITemplateDescr, stateUpdater: StateUpdater)
 
                 return React.createElement<ITemplateWrapperProps>(TemplateWrapper, {
                     content: contentProvider(data.model),
-                    container: unwrapElement(data.container),
+                    container,
                     onRemoved: () => stateUpdater((t) => delete t[templateId]),
                     onRendered: data.onRendered,
                     key: templateId
                 });
             };
             stateUpdater((t) => t[templateId] = createWrapper);
+            return container;
         }
     };
 }
diff --git a/src/core/template-wrapper.ts b/src/core/template-wrapper.ts
index a2736e5..24eac1e 100644
--- a/src/core/template-wrapper.ts
+++ b/src/core/template-wrapper.ts
@@ -23,14 +23,14 @@ class TemplateWrapper extends React.PureComponent<ITemplateWrapperProps, object>
             const onRendered: () => void = this.props.onRendered;
             setTimeout(() => onRendered());
         }
-
-        const templateElement = ReactDOM.findDOMNode(this);
-        const templateParent = templateElement && templateElement.parentNode;
-
-        const isGridRow = templateParent && templateParent.nodeName !== "DIV";
-        const element: Element = (isGridRow ? templateElement : templateParent) as Element;
-
-        events.one(element, DX_REMOVE_EVENT, this.props.onRemoved);
+        const restoreRemovedContent = () => {
+            // Let React remove it itself
+            this.props.container.appendChild(ReactDOM.findDOMNode(this));
+        };
+        events.one(this.props.container, DX_REMOVE_EVENT, () => {
+            restoreRemovedContent();
+            this.props.onRemoved();
+        });
     }
 }
 


Test patch:
diff --git a/src/core/__tests__/template.test.tsx b/src/core/__tests__/template.test.tsx
index c5ca30b..de1b91d 100644
--- a/src/core/__tests__/template.test.tsx
+++ b/src/core/__tests__/template.test.tsx
@@ -87,6 +87,29 @@ describe("function template", () => {
         expect(component.find(".template").html()).toBe('<div class="template">Template with data</div>');
     });
 
+    it("renders template wrapper for simple item", () => {
+        const itemRender: any = jest.fn((text: string) => <div>Template {text}</div>);
+        const component = mount(
+            <ComponentWithTemplates itemRender={itemRender} />
+        );
+        const container = document.createElement("div");
+        renderItemTemplate("with data", container);
+        component.update();
+        expect(container.innerHTML).toBe('<div class="dx-template-wrapper"><div>Template with data</div></div>');
+    });
+
+    it("renders template for table", () => {
+        const itemRender: any = (text: string) => <tbody><tr><td>Template {text}</td></tr></tbody>;
+        const component = mount(
+            <ComponentWithTemplates itemRender={itemRender} />
+        );
+        const container = document.createElement("table");
+        renderItemTemplate("with data", container);
+        component.update();
+        expect(container.innerHTML)
+            .toBe("<tbody><tr><td>Template with data</td></tr></tbody>");
+    });
+
     it("calls onRendered callback", () => {
         const itemRender: any = (text: string) => <div className={"template"}>Template {text}</div>;
         const onRendered: () => void = jest.fn();
@@ -224,7 +247,7 @@ describe("nested template", () => {
         expect(component.find(".template").html()).toBe('<div class="template">Template</div>');
     });
 
-    it("renders nested templates", () => {
+    it("renders new templates after component change", () => {
         const FirstTemplate = () => <div className={"template"}>First Template</div>;
         const component = mount(
             <ComponentWithTemplates>
@@ -406,7 +429,10 @@ it("removes deleted nodes from state", () => {
     renderItemTemplate();
     expect(Object.getOwnPropertyNames(component.state("templates")).length).toBe(1);
     component.update();
-    events.triggerHandler(component.find(".template").getDOMNode().parentNode, "dxremove");
+    const templateContent = component.find(".template").getDOMNode();
+    const parentNode = templateContent.parentNode;
+    parentNode!.removeChild(templateContent);
+    events.triggerHandler(parentNode, "dxremove");
     component.update();
     expect(Object.getOwnPropertyNames(component.state("templates")).length).toBe(0);
 });