Repository: DevExpress/devextreme-react
Pull request #68 (2018-04-20 10:36:55)

Mark templates as async


Patch:
diff --git a/example/accordion-example.tsx b/example/accordion-example.tsx
new file mode 100644
index 0000000..134915a
--- /dev/null
+++ b/example/accordion-example.tsx
@@ -0,0 +1,41 @@
+import * as React from "react";
+
+import DataSource from "devextreme/data/data_source";
+
+import { Accordion } from "../src/ui/accordion";
+
+import Example from "./example-block";
+
+interface IAccordionItemProps {
+    text: string;
+    title: string;
+}
+
+const items: IAccordionItemProps[] = [
+    { text: "123", title: "title123" },
+    { text: "234", title: "title234" },
+    { text: "567", title: "title567" }
+];
+
+const ItemTitle = (data: IAccordionItemProps) => <div style={{height: 100}}>{data.title}</div>;
+
+export default class extends React.Component<any, any> {
+    public render() {
+        return (
+            <Example title="DxAccordion" >
+                <hr />
+                <h4>Simple Accordion</h4>
+                <Accordion
+                    items={items}
+                />
+                <hr />
+                <h4>Accordion with itemTitleRender</h4>
+                <Accordion
+                    collapsible={true}
+                    items={items}
+                    itemTitleRender={ItemTitle}
+                />
+            </Example>
+        );
+    }
+}
\ No newline at end of file
diff --git a/example/app.tsx b/example/app.tsx
index 94bcfff..a703ad5 100644
--- a/example/app.tsx
+++ b/example/app.tsx
@@ -4,6 +4,7 @@ import * as ReactDOM from "react-dom";
 import "devextreme/dist/css/dx.common.css";
 import "devextreme/dist/css/dx.light.compact.css";
 
+import AccordionExample from "./accordion-example";
 import ChartExample from "./chart-example";
 import DataGridExample from "./data-grid-example";
 import Example from "./example-block";
@@ -11,7 +12,7 @@ import FormExample from "./form-example";
 import ListExample from "./list-example";
 import PopupExample from "./popup-example";
 import ScrollViewExample from "./scroll-view-example";
-import TexBoxExample from "./text-box-example";
+import TextBoxExample from "./text-box-example";
 
 import { Button, NumberBox, Scheduler } from "../src";
 
@@ -26,7 +27,7 @@ ReactDOM.render(
 
     <PopupExample />
 
-    <TexBoxExample />
+    <TextBoxExample />
 
     <Example title="DxNumberBox">
       <NumberBox step={50} min={50} showSpinButtons={true} />
@@ -38,6 +39,8 @@ ReactDOM.render(
 
     <ListExample />
 
+    <AccordionExample />
+
     <DataGridExample/>
 
     <Example title="DxScheduler">
diff --git a/example/list-example.tsx b/example/list-example.tsx
index f1faca1..8bb0be5 100644
--- a/example/list-example.tsx
+++ b/example/list-example.tsx
@@ -1,6 +1,8 @@
 import * as React from "react";
 
 import DataSource from "devextreme/data/data_source";
+import { Template } from "../src/core/template";
+
 import { Button } from "../src/ui/button";
 import { List } from "../src/ui/list";
 import { TextBox } from "../src/ui/text-box";
@@ -81,6 +83,13 @@ export default class extends React.Component<any, { text: string; items: IListIt
                     items={this.state.items}
                     itemComponent={Item}
                 />
+                <h4>List with standalone template</h4>
+                <List
+                    items={this.state.items}
+                >
+                    <Template name={"item"} component={Item}/>
+                </List>
+
                 <hr />
                 <h4>List with dataSource</h4>
                 <List dataSource={this.dataSource} />
diff --git a/src/core/component.ts b/src/core/component.ts
index 2255483..4a0e19c 100644
--- a/src/core/component.ts
+++ b/src/core/component.ts
@@ -1,12 +1,10 @@
 import * as React from "react";
-import * as ReactDOM from "react-dom";
 
 import * as events from "devextreme/events";
 
-const DX_TEMPLATE_WRAPPER_CLASS = "dx-template-wrapper";
-const DX_REMOVE_EVENT = "dxremove";
+import { prepareTemplate } from "./template-provider";
 
-const generateID = () => Math.random().toString(36).substr(2);
+const DX_REMOVE_EVENT = "dxremove";
 
 interface ITemplateMeta {
   tmplOption: string;
@@ -65,6 +63,7 @@ export default class Component<P> extends React.PureComponent<P, any> {
     const splitProps = this._splitComponentProps(props);
 
     const options: Record<string, any> = {
+      templatesRenderAsynchronously: true,
       ...splitProps.defaults,
       ...splitProps.options,
       ...this._getIntegrationOptions(splitProps.templates)
@@ -184,15 +183,13 @@ export default class Component<P> extends React.PureComponent<P, any> {
     };
 
     this._templateProps.forEach((m) => {
-      if (options[m.component]) {
+      if (options[m.component] || options[m.render]) {
         result[m.tmplOption] = m.tmplOption;
-        templates[m.tmplOption] =
-          this._fillTemplate(React.createElement.bind(this, options[m.component]));
-      }
-      if (options[m.render]) {
-        result[m.tmplOption] = m.tmplOption;
-        templates[m.tmplOption] =
-          this._fillTemplate(options[m.render].bind(this));
+        const templateProp = !!options[m.component] ?
+          React.createElement.bind(this, options[m.component]) :
+          options[m.render].bind(this);
+
+        templates[m.tmplOption] = prepareTemplate(templateProp, this);
       }
     });
 
@@ -200,36 +197,4 @@ export default class Component<P> extends React.PureComponent<P, any> {
       return result;
     }
   }
-
-  private _fillTemplate(tmplFn: any): object {
-    return {
-      render: (data: any) => {
-        const element = document.createElement("div");
-        element.className = DX_TEMPLATE_WRAPPER_CLASS;
-        data.container.appendChild(element);
-
-        const templateId = "__template_" + generateID();
-        events.one(element, DX_REMOVE_EVENT, () => {
-          this.setState((state: any) => {
-            const updatedTemplates = {...state.templates};
-            delete updatedTemplates[templateId];
-            return {
-              templates : updatedTemplates
-            };
-          });
-        });
-
-        const portal: any = () => ReactDOM.createPortal(tmplFn(data.model), element);
-
-        this.setState((state: any) => {
-          const updatedTemplates = {...state.templates};
-          updatedTemplates[templateId] = portal;
-          return {
-            templates : updatedTemplates
-          };
-        });
-        return element;
-      }
-    };
-  }
 }
diff --git a/src/core/template-provider.ts b/src/core/template-provider.ts
new file mode 100644
index 0000000..f385454
--- /dev/null
+++ b/src/core/template-provider.ts
@@ -0,0 +1,40 @@
+import * as ReactDOM from "react-dom";
+
+import * as events from "devextreme/events";
+
+const DX_TEMPLATE_WRAPPER_CLASS = "dx-template-wrapper";
+const DX_REMOVE_EVENT = "dxremove";
+
+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 templateId = "__template_" + generateID();
+      events.one(element, DX_REMOVE_EVENT, () => {
+        component.setState((state: any) => {
+          const updatedTemplates = {...state.templates};
+          delete updatedTemplates[templateId];
+          return {
+            templates : updatedTemplates
+          };
+        });
+      });
+
+      const portal: any = () => ReactDOM.createPortal(tmplFn(data.model), element);
+
+      component.setState((state: any) => {
+        const updatedTemplates = {...state.templates};
+        updatedTemplates[templateId] = portal;
+        return {
+          templates : updatedTemplates
+        };
+      });
+      return element;
+    }
+  };
+}


Test patch:
diff --git a/src/core/component.test.tsx b/src/core/component.test.tsx
index 250e917..0b42157 100644
--- a/src/core/component.test.tsx
+++ b/src/core/component.test.tsx
@@ -50,6 +50,14 @@ describe("component rendering", () => {
         expect(WidgetClass.mock.instances.length).toBe(1);
     });
 
+    it("pass templatesRenderAsynchronously to widgets", () => {
+        shallow(
+            <TestComponent />
+        );
+
+        expect(WidgetClass.mock.calls[0][1]).toEqual({templatesRenderAsynchronously: true});
+    });
+
     it("does not create empty children prop", () => {
         const component = shallow(
             <TestComponent />
@@ -76,7 +84,7 @@ describe("component rendering", () => {
             </TestComponent>
         );
 
-        expect(WidgetClass.mock.calls[1][1]).toEqual({});
+        expect(WidgetClass.mock.calls[1][1].children).toBeUndefined();
     });
 });