Repository: DevExpress/devextreme-react (js)

Issue:
React dxDataGrid options such as rowComponent and rowRender has no effect
<!--
  Note: if the issue is a general React question, please check React Docs first: https://reactjs.org/docs
  If your question is about  DevExtreme widgets' APIs, use the DevExpress Support Center: https://www.devexpress.com/Support/Center
-->

### Issue Type
<!-- Do you want to request a feature or report a bug? Check one option with "x" -->
- [x] bug report
- [ ] feature request

### Current Behavior
Setting rowComponent option doesn't have any clear effect. Option rowComponent={()=>{ debugger; }}  will not even stop the script execution at the target debugger point.
### Expected Behavior
Expected that with this option i can map row-item from dataSource, and return my own rowComponent. 
### Steps to Reproduce
<!-- If the current behavior is a bug, please provide the steps to reproduce -->
Just try to set rowComponent option, for example like function with debugger, to make sure that this option doesn't work even in the trivial case
### Demo
<!--
  If you provide a minimal demo of the problem\proposal, it helps us to solve it much faster.
  Paste the link to your online code editor (like https://stackblitz.com) below:
-->
https://stackblitz.com/edit/devextreme-react-pxaqrw
### Environment Details
<!--
Specify DevExtreme version, DevExtreme React Components version, React version and other environment details or notes you consider important.
-->
devextreme 18.1.1-pre-18093
devextreme-react 18.1.1-alpha.3
react 16.3.0

Pull request description:
Refactor template rendering
fix #64 

Patch:
diff --git a/src/core/template-provider.ts b/src/core/template-provider.ts
index f385454..bc0149f 100644
--- a/src/core/template-provider.ts
+++ b/src/core/template-provider.ts
@@ -1,40 +1,47 @@
-import * as ReactDOM from "react-dom";
+import * as React from "react";
 
-import * as events from "devextreme/events";
-
-const DX_TEMPLATE_WRAPPER_CLASS = "dx-template-wrapper";
-const DX_REMOVE_EVENT = "dxremove";
+import TemplateWrapper from "./template-wrapper";
 
 const generateID = () => Math.random().toString(36).substr(2);
 
-export function prepareTemplate(tmplFn: any, component: React.Component): object {
-  return {
-    render: (data: any) => {
-      const element = document.createElement("div");
-      element.className = DX_TEMPLATE_WRAPPER_CLASS;
-      data.container.appendChild(element);
+const unwrapElement = (element: any) =>  element.get ? element.get(0) : element;
+
+export interface ITemplateData {
+  container: any;
+  model?: any;
+  index?: any;
+}
 
+export function prepareTemplate(tmplFn: any, component: React.Component): { render: (data: ITemplateData) => any } {
+  return {
+    render: (data: ITemplateData) => {
       const templateId = "__template_" + generateID();
-      events.one(element, DX_REMOVE_EVENT, () => {
-        component.setState((state: any) => {
+      const removedHandler = () => {
+      component.setState((state: any) => {
           const updatedTemplates = {...state.templates};
           delete updatedTemplates[templateId];
           return {
-            templates : updatedTemplates
+            templates: updatedTemplates
           };
         });
-      });
+      };
 
-      const portal: any = () => ReactDOM.createPortal(tmplFn(data.model), element);
+      const templateWrapper: any = () =>
+        React.createElement(TemplateWrapper, {
+          render: tmplFn,
+          data: data.model,
+          container: unwrapElement(data.container),
+          onRemoved: removedHandler,
+          key: templateId
+        });
 
       component.setState((state: any) => {
         const updatedTemplates = {...state.templates};
-        updatedTemplates[templateId] = portal;
+        updatedTemplates[templateId] = templateWrapper;
         return {
-          templates : updatedTemplates
+          templates: updatedTemplates
         };
       });
-      return element;
     }
   };
 }
diff --git a/src/core/template-wrapper.ts b/src/core/template-wrapper.ts
new file mode 100644
index 0000000..f883621
--- /dev/null
+++ b/src/core/template-wrapper.ts
@@ -0,0 +1,22 @@
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+
+import * as events from "devextreme/events";
+
+const DX_REMOVE_EVENT = "dxremove";
+
+export default class TemplateWrapper extends React.PureComponent<{
+    render: any
+    data: any,
+    container: Element,
+    onRemoved: () => void
+}, any> {
+    public render() {
+        return ReactDOM.createPortal(this.props.render(this.props.data), this.props.container);
+    }
+
+    public componentDidMount() {
+        const element = ReactDOM.findDOMNode(this);
+        events.one(element, DX_REMOVE_EVENT, this.props.onRemoved);
+    }
+}


Test patch:
diff --git a/src/core/component.test.tsx b/src/core/component.test.tsx
index 0b42157..d7068e3 100644
--- a/src/core/component.test.tsx
+++ b/src/core/component.test.tsx
@@ -89,15 +89,13 @@ describe("component rendering", () => {
 });
 
 describe("templates", () => {
-
-    const DX_TEMPLATE_WRAPPER = "dx-template-wrapper";
-
-    const renderItemTemplate: (model: any) => Element = (model: object) => {
+    function renderItemTemplate(model: any, container?: any): Element {
+        container = container || document.createElement("div");
         const render = WidgetClass.mock.calls[0][1].integrationOptions.templates.item.render;
         return render({
-            container: document.createElement("div"), model
+            container, model
         });
-    };
+    }
 
     describe("function template", () => {
 
@@ -119,26 +117,39 @@ describe("templates", () => {
         });
 
         it("renders", () => {
-            const itemRender: any = jest.fn((props: any) => <div>Template {props.text}</div>);
-            mount(
+            const itemRender: any = jest.fn((props: any) => <div className={"template"}>Template {props.text}</div>);
+            const component = mount(
                 <ComponentWithTemplates itemRender={itemRender} />
             );
-            const renderedTemplate = renderItemTemplate({ text: "with data" });
+            renderItemTemplate({ text: "with data" });
 
             expect(itemRender).toBeCalled();
-            expect(renderedTemplate.className).toBe(DX_TEMPLATE_WRAPPER);
-            expect(renderedTemplate.innerHTML).toBe("<div>Template with data</div>");
+            component.update();
+            expect(component.find(".template").html()).toBe('<div class="template">Template with data</div>');
+        });
+
+        it("renders inside unwrapped container", () => {
+            function itemRender() {
+                return <div className={"template"}>Template</div>;
+            }
+            const component = mount(
+                <ComponentWithTemplates itemRender={itemRender} />
+            );
+            renderItemTemplate({}, { get: () => document.createElement("div")});
+            component.update();
+            expect(component.find(".template").html()).toBe('<div class="template">Template</div>');
         });
 
         it("renders simple item", () => {
-            const itemRender: any = jest.fn((text: any) => <div>Template {text}</div>);
-            mount(
+            const itemRender: any = jest.fn((text: string) => <div className={"template"}>Template {text}</div>);
+            const component = mount(
                 <ComponentWithTemplates itemRender={itemRender} />
             );
-            const renderedTemplate = renderItemTemplate("with data");
+            renderItemTemplate("with data");
 
             expect(itemRender).toBeCalled();
-            expect(renderedTemplate.innerHTML).toBe("<div>Template with data</div>");
+            component.update();
+            expect(component.find(".template").html()).toBe('<div class="template">Template with data</div>');
         });
 
         it("renders inside component", () => {
@@ -174,14 +185,14 @@ describe("templates", () => {
         });
 
         it("renders", () => {
-            const ItemTemplate = (props: any) => <div>Template {props.text}</div>;
-            mount(
+            const ItemTemplate = (props: any) => <div className={"template"}>Template {props.text}</div>;
+            const component = mount(
                 <ComponentWithTemplates itemComponent={ItemTemplate} />
             );
 
-            const renderedTemplate = renderItemTemplate({ text: "with data" });
-            expect(renderedTemplate.className).toBe(DX_TEMPLATE_WRAPPER);
-            expect(renderedTemplate.innerHTML).toBe("<div>Template with data</div>");
+            renderItemTemplate({ text: "with data" });
+            component.update();
+            expect(component.find(".template").html()).toBe('<div class="template">Template with data</div>');
         });
 
         it("renders inside component", () => {
@@ -225,7 +236,7 @@ describe("templates", () => {
         renderItemTemplate({});
         expect(Object.getOwnPropertyNames(component.state("templates")).length).toBe(1);
         component.update();
-        events.triggerHandler(component.find(".template").getDOMNode().parentNode, "dxremove");
+        events.triggerHandler(component.find(".template").getDOMNode(), "dxremove");
         component.update();
         expect(Object.getOwnPropertyNames(component.state("templates")).length).toBe(0);
     });