Repository: DevExpress/devextreme-react
Pull request #739 (2022-07-19 12:22:04)

Template fix : the number of instances increases (T1097917)
Scheduler - The number of dataCellRender function calls increases each time the parent component is re-rendered (T1097917)

fix: In some cases (for example: setting more than one prop in one useCallback ) template component is removed before it's got on onRemove listener, therefore it's instance keeps in templatesStore. For fix it we subscribe on dxRemove event on container;

Co-authored-by: volvl <dxvladislavvolkov@users.noreply.github.com>


Patch:
diff --git a/packages/devextreme-react/src/core/dx-template.ts b/packages/devextreme-react/src/core/dx-template.ts
index 6cdf8b6..653aace 100644
--- a/packages/devextreme-react/src/core/dx-template.ts
+++ b/packages/devextreme-react/src/core/dx-template.ts
@@ -4,6 +4,8 @@ import { DoubleKeyMap, generateID } from './helpers';
 import { ITemplateArgs } from './template';
 import { ITemplateWrapperProps, TemplateWrapper } from './template-wrapper';
 import { TemplatesStore } from './templates-store';
+import {DX_REMOVE_EVENT} from "./component-base";
+import * as events from 'devextreme/events';
 
 interface IDxTemplate {
   render: (data: IDxTemplateData) => any;
@@ -35,6 +37,23 @@ function createDxTemplate(
 
       let templateId: string;
 
+      const onRemoved = (): void => {
+        templatesStore.setDeferredRemove(templateId, true);
+        renderedTemplates.delete(key);
+      };
+
+      const _subscribeOnContainerRemoval = (): void =>  {
+        if (container.nodeType === Node.ELEMENT_NODE) {
+          events.one(container, DX_REMOVE_EVENT, onRemoved);
+        }
+      }
+
+      const _unsubscribeOnContainerRemoval = (): void =>  {
+        if (container.nodeType === Node.ELEMENT_NODE) {
+          events.off(container, DX_REMOVE_EVENT, onRemoved);
+        }
+      }
+
       if (prevTemplateId) {
         templateId = prevTemplateId;
       } else {
@@ -44,7 +63,11 @@ function createDxTemplate(
           renderedTemplates.set(key, templateId);
         }
       }
+
+      _subscribeOnContainerRemoval();
+
       templatesStore.add(templateId, () => {
+
         const props: ITemplateArgs = {
           data: data.model,
           index: data.index,
@@ -56,11 +79,9 @@ function createDxTemplate(
           {
             content: contentProvider(props),
             container,
-            onRemoved: () => {
-              templatesStore.setDeferredRemove(templateId, true);
-              renderedTemplates.delete({ key1: data.model, key2: container });
-            },
+            onRemoved,
             onDidMount: () => {
+              _unsubscribeOnContainerRemoval();
               templatesStore.setDeferredRemove(templateId, false);
               data.onRendered?.();
             },


Test patch:
diff --git a/packages/devextreme-react/src/core/__tests__/template.test.tsx b/packages/devextreme-react/src/core/__tests__/template.test.tsx
index 7c09432..440a4bb 100644
--- a/packages/devextreme-react/src/core/__tests__/template.test.tsx
+++ b/packages/devextreme-react/src/core/__tests__/template.test.tsx
@@ -335,6 +335,32 @@ function testTemplateOption(testedOption: string) {
     expect(templatesKeys[0]).not.toBe(templatesKeys[1]);
   });
 
+  it('removes previous template if its container was removed', () => {
+    const ref = React.createRef() as React.RefObject<ComponentWithTemplates>;
+
+    const elementOptions: Record<string, any> = {};
+    elementOptions[testedOption] = prepareTemplate((data: any) => (
+        <div className="template">
+          Template
+          {data.text}
+        </div>
+    ));
+    render(
+        <ComponentWithTemplates {...elementOptions} ref={ref} />,
+    );
+
+    const componentInstance = ref.current as unknown as {
+      _templatesStore: { _templates: Record<string, TemplateWrapperRenderer> } };
+
+    const container = document.createElement('div');
+    renderItemTemplate({ text: 1 }, container);
+    events.triggerHandler(container, 'dxremove');
+    renderItemTemplate({ text: 1 }, document.createElement('div'));
+
+    const templatesKeys = Object.getOwnPropertyNames(componentInstance._templatesStore._templates);
+    expect(templatesKeys.length).toBe(1);
+  });
+
   it('has templates with ids genetated with keyExpr', () => {
     const ref = React.createRef() as React.RefObject<ComponentWithTemplates>;