Repository: DevExpress/devextreme-react
Pull request #425 (2020-06-06 11:47:22)

Bump websocket-extensions from 0.1.3 to 0.1.4
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md">websocket-extensions's changelog</a>.</em></p>
<blockquote>
<h3>0.1.4 / 2020-06-02</h3>
<ul>
<li>Remove a ReDoS vulnerability in the header parser (CVE-2020-7662, reported by
Robert McLaughlin)</li>
<li>Change license from MIT to Apache 2.0</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/8efd0cd6e35faf9bb9cb08759be1e27082177d43"><code>8efd0cd</code></a> Bump version to 0.1.4</li>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/3dad4ad44a8c5f74d4f8f4efd3f9d6e0b5df3051"><code>3dad4ad</code></a> Remove ReDoS vulnerability in the Sec-WebSocket-Extensions header parser</li>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/4a76c75efb1c5d6a2f60550e9501757458d19533"><code>4a76c75</code></a> Add Node versions 13 and 14 on Travis</li>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/44a677a9c0631daed0b0f4a4b68c095b624183b8"><code>44a677a</code></a> Formatting change: {...} should have spaces inside the braces</li>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/f6c50aba0c20ff45b0f87cea33babec1217ec3f5"><code>f6c50ab</code></a> Let npm reformat package.json</li>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/2d211f3705d52d9efb4f01daf5a253adf828592e"><code>2d211f3</code></a> Change markdown formatting of docs.</li>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/0b620834cc1e1f2eace1d55ab17f71d90d88271d"><code>0b62083</code></a> Update Travis target versions.</li>
<li><a href="https://github.com/faye/websocket-extensions-node/commit/729a4653073fa8dd020561113513bfa2e2119415"><code>729a465</code></a> Switch license to Apache 2.0.</li>
<li>See full diff in <a href="https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=websocket-extensions&package-manager=npm_and_yarn&previous-version=0.1.3&new-version=0.1.4)](https://help.github.com/articles/configuring-automated-security-fixes)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/DevExpress/devextreme-react/network/alerts).

</details>


Patch:
diff --git a/.eslintrc b/.eslintrc
index a61b4b9..c44f165 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -7,61 +7,22 @@
         "node": true,
         "jest": true
     },
+    "ignorePatterns": ["src/*.ts", "src/ui/*.ts"],
     "parserOptions": {
         "project": ["./tsconfig.json", "./example/tsconfig.json"]
     },
     "rules": {
-        "arrow-body-style": "off",
-        "array-bracket-spacing": "off",
-        "array-callback-return": "off",
-        "class-methods-use-this": "off",
-        "consistent-return": "off",
-        "function-paren-newline": "off",
-        "implicit-arrow-linebreak": "off",
-        "import/extensions": "off",
         "import/no-cycle": "off",
-        "import/no-extraneous-dependencies": "off",
-        "import/no-named-as-default": "off",
-        "import/no-useless-path-segments": "off",
-        "import/order": "off",
-        "jsx-a11y/no-static-element-interactions": "off",
-        "jsx-a11y/click-events-have-key-events": "off",
-        "max-classes-per-file": "off",
-        "max-len": "off",
         "no-param-reassign": "off",
-        "no-undef": "off",
         "no-underscore-dangle": "off",
-        "no-extra-boolean-cast": "off",
-        "no-alert": "off",
-        "no-continue": "off",
-        "no-control-regex": "off",
-        "no-multi-spaces": "off",
-        "no-plusplus": "off",
-        "no-prototype-builtins": "off",
-        "no-useless-concat": "off",
-        "no-useless-return": "off",
-        "no-restricted-syntax": "off",
-        "no-return-assign": "off",
-        "no-useless-escape": "off",
-        "object-curly-newline": "off",
-        "object-curly-spacing": "off",
-        "operator-linebreak": "off",
-        "padded-blocks": "off",
-        "prefer-template": "off",
-        "prefer-destructuring": "off",
-        "react/forbid-prop-types": "off",
-        "react/no-unescaped-entities": "off",
-        "react/no-access-state-in-setstate": "off",
-        "react/no-children-prop": "off",
-        "react/no-unused-state": "off",
-        "react/prefer-stateless-function": "off",
-        "react/require-default-props": "off",
-        "react/static-property-placement": "off",
-        "space-unary-ops": "off",
-        "spaced-comment": "off",
-        "@typescript-eslint/no-explicit-any": "off",
-        "@typescript-eslint/no-use-before-define": "off",
-        "@typescript-eslint/no-shadow": "off",
-        "@typescript-eslint/explicit-module-boundary-types": "off"
-    }
+        "@typescript-eslint/no-explicit-any": "off"
+    },
+    "overrides": [
+        {
+            "files": [ "./example/*.ts", "./example/*.tsx" ],
+            "rules": {
+                "import/extensions": "off"
+            }
+        }
+    ],
 }
diff --git a/.gitignore b/.gitignore
index 7a56c26..10eb342 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ src/*
 !src/core
 src/index.ts
 tools/bin/
-artifacts/
\ No newline at end of file
+artifacts/
+coverage/
diff --git a/example/accordion-example.tsx b/example/accordion-example.tsx
index fab568b..aace9c7 100644
--- a/example/accordion-example.tsx
+++ b/example/accordion-example.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
 
-import Accordion from '../src/accordion';
+import { Accordion } from '../src/accordion';
 
 import Example from './example-block';
 
@@ -15,25 +15,21 @@ const items: IAccordionItemProps[] = [
   { text: '567', title: 'title567' },
 ];
 
-const ItemTitle = (data: IAccordionItemProps) => <div style={{height: 100}}>{data.title}</div>;
+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
-          items={items}
-          itemTitleRender={ItemTitle}
-        />
-      </Example>
-    );
-  }
-}
+export default (): React.ReactElement | null => (
+  <Example title="DxAccordion">
+    <hr />
+    <h4>Simple Accordion</h4>
+    <Accordion
+      items={items}
+    />
+    <hr />
+    <h4>Accordion with itemTitleRender</h4>
+    <Accordion
+      collapsible
+      items={items}
+      itemTitleRender={ItemTitle}
+    />
+  </Example>
+);
diff --git a/example/app.tsx b/example/app.tsx
index c9f22d2..5b38fc6 100644
--- a/example/app.tsx
+++ b/example/app.tsx
@@ -69,7 +69,7 @@ ReactDOM.render(
     <ChartExample />
 
     <Example title="Element attributes">
-      <Button text="Button with style attr" style={{backgroundColor: '#ffc'}} />
+      <Button text="Button with style attr" style={{ backgroundColor: '#ffc' }} />
     </Example>
 
     <Example title="SelectBox example">
diff --git a/example/box-example.tsx b/example/box-example.tsx
index 91387a2..84638ac 100644
--- a/example/box-example.tsx
+++ b/example/box-example.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 
-import Box, { Item } from '../src/box';
-import Button from '../src/button';
+import { Box, Item } from '../src/box';
+import { Button } from '../src/button';
 import Example from './example-block';
 
 const initialState = {
@@ -18,15 +18,14 @@ const initialState = {
 };
 
 class App extends React.Component<any, typeof initialState> {
-
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
 
     this.state = { ...initialState };
   }
 
   private renderItems = () => {
-    const {items} = this.state;
+    const { items } = this.state;
     return items.map((item) => (
       <Item key={item.id} ratio={1}>
         {item.name}
@@ -35,7 +34,7 @@ class App extends React.Component<any, typeof initialState> {
   };
 
   private add = () => {
-    const {items: stateItems} = this.state;
+    const { items: stateItems } = this.state;
     const items = [...stateItems];
     const id = items.length ? items[items.length - 1].id + 1 : 1;
     items.push({
@@ -47,12 +46,12 @@ class App extends React.Component<any, typeof initialState> {
   };
 
   private remove = () => {
-    const {items: stateItems} = this.state;
+    const { items: stateItems } = this.state;
     const items = stateItems.slice(0, stateItems.length - 1);
     this.setState({ items });
   };
 
-  public render() {
+  public render(): React.ReactNode {
     return (
       <Example title="Box example">
         <Button
diff --git a/example/chart-example.tsx b/example/chart-example.tsx
index 5901df4..4e17c71 100644
--- a/example/chart-example.tsx
+++ b/example/chart-example.tsx
@@ -1,11 +1,13 @@
+/* eslint-disable max-classes-per-file */
+/* eslint-disable react/no-unused-state */
 import * as React from 'react';
 
 import { orangesByDay } from './data';
 import Example from './example-block';
 
-import Button from '../src/button';
-import Chart from '../src/chart';
-import TextBox from '../src/text-box';
+import { Button } from '../src/button';
+import { Chart } from '../src/chart';
+import { TextBox } from '../src/text-box';
 
 interface IState {
   currentTime: string;
@@ -13,9 +15,46 @@ interface IState {
   series: any[];
 }
 
-export default class extends React.Component<any, IState> {
+class Updater extends React.Component<{ onChange: (value: string) => void }, { value: string }> {
+  constructor(props: { onChange: (value: string) => void }) {
+    super(props);
+    this.state = {
+      value: '',
+    };
 
-  constructor(props: any) {
+    this.update = this.update.bind(this);
+    this.fireOnChange = this.fireOnChange.bind(this);
+  }
+
+  private fireOnChange() {
+    const { onChange } = this.props;
+    const { value } = this.state;
+    onChange(value);
+  }
+
+  private update(e: any) {
+    this.setState({
+      value: e.value,
+    });
+  }
+
+  public render() {
+    const { value } = this.state;
+    return (
+      <div className="dx-field">
+        <div className="dx-field-label">
+          <TextBox value={value} onValueChanged={this.update} valueChangeEvent="input" />
+        </div>
+        <div className="dx-field-value">
+          <Button text="Update series name" onClick={this.fireOnChange} />
+        </div>
+      </div>
+    );
+  }
+}
+
+export default class extends React.Component<any, IState> {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       currentTime: this.GetTimeString(),
@@ -41,7 +80,7 @@ export default class extends React.Component<any, IState> {
   }
 
   private updateSeriesName(seriesName: string) {
-    const {series: stateSeries} = this.state;
+    const { series: stateSeries } = this.state;
     const series = [...stateSeries];
     series[0].name = seriesName;
     this.setState({
@@ -50,8 +89,8 @@ export default class extends React.Component<any, IState> {
     });
   }
 
-  public render() {
-    const {currentTime, series} = this.state;
+  public render(): React.ReactNode {
+    const { currentTime, series } = this.state;
     return (
       <Example title="DxChart" state={this.state}>
 
@@ -74,42 +113,3 @@ export default class extends React.Component<any, IState> {
     );
   }
 }
-
-class Updater extends React.Component<{ onChange: (value: string) => void }, { value: string }> {
-
-  constructor(props: { onChange: (value: string) => void }) {
-    super(props);
-    this.state = {
-      value: '',
-    };
-
-    this.update = this.update.bind(this);
-    this.fireOnChange = this.fireOnChange.bind(this);
-  }
-
-  private fireOnChange() {
-    const {onChange} = this.props;
-    const {value} = this.state;
-    onChange(value);
-  }
-
-  private update(e: any) {
-    this.setState({
-      value: e.value,
-    });
-  }
-
-  public render() {
-    const {value} = this.state;
-    return (
-      <div className="dx-field">
-        <div className="dx-field-label">
-          <TextBox value={value} onValueChanged={this.update} valueChangeEvent="input" />
-        </div>
-        <div className="dx-field-value">
-          <Button text="Update series name" onClick={this.fireOnChange} />
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/example/data-grid-example.tsx b/example/data-grid-example.tsx
index 20bcdc4..1bd2c81 100644
--- a/example/data-grid-example.tsx
+++ b/example/data-grid-example.tsx
@@ -3,7 +3,8 @@ import Example from './example-block';
 
 import { Template } from '../src/core/template';
 
-import DataGrid, {
+import {
+  DataGrid,
   Column,
   FilterRow,
   Grouping,
@@ -13,33 +14,30 @@ import DataGrid, {
   Paging,
   Selection,
 } from '../src/data-grid';
-import NumberBox from '../src/number-box';
+import { NumberBox } from '../src/number-box';
 
 import { sales } from './data';
 
-const DetailComponent = ({ data: { data } }: any) => {
-  return (
-    <p>
-      Row data:
-      <br />
-      {JSON.stringify(data)}
-    </p>
-  );
-};
+const DetailComponent = ({ data: { data } }: any) => (
+  <p>
+    Row data:
+    <br />
+    {JSON.stringify(data)}
+  </p>
+);
 
 const CityComponent = (props: any) => {
-  const {data} = props;
+  const { data } = props;
   return <i>{data.displayValue}</i>;
 };
 
 const RegionComponent = (props: any) => {
-  const {data} = props;
+  const { data } = props;
   return <b>{data.displayValue}</b>;
 };
 
 export default class extends React.Component<any, { expandAll: boolean, pageSize: number }> {
-
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       expandAll: true,
@@ -78,8 +76,8 @@ export default class extends React.Component<any, { expandAll: boolean, pageSize
     });
   }
 
-  public render() {
-    const {expandAll, pageSize} = this.state;
+  public render(): React.ReactNode {
+    const { expandAll, pageSize } = this.state;
     return (
       <Example title="DxDataGrid" state={this.state}>
         <br />
diff --git a/example/editor-example.tsx b/example/editor-example.tsx
index dbf7171..48b5611 100644
--- a/example/editor-example.tsx
+++ b/example/editor-example.tsx
@@ -10,7 +10,7 @@ function fieldRender() {
 }
 
 export default class extends React.Component<any, any> {
-  public render() {
+  public render(): React.ReactNode {
     return (
       <Example title="Editors" state={this.state}>
         <SelectBox fieldRender={fieldRender} />
diff --git a/example/example-block.tsx b/example/example-block.tsx
index b298e62..bbd0ee9 100644
--- a/example/example-block.tsx
+++ b/example/example-block.tsx
@@ -6,10 +6,10 @@ interface IProps {
   children: any;
 }
 
-const Example = (props: IProps) => {
-  const {state, title, children} = props;
+const Example = (props: IProps): JSX.Element => {
+  const { state, title, children } = props;
   let stateBlock = null;
-  if (!!state) {
+  if (state) {
     stateBlock = <pre className="example-state">{JSON.stringify(state, null, '  ')}</pre>;
   }
 
@@ -24,4 +24,8 @@ const Example = (props: IProps) => {
   );
 };
 
+Example.defaultProps = {
+  state: {},
+};
+
 export default Example;
diff --git a/example/form-example.tsx b/example/form-example.tsx
index 315f16c..4735283 100644
--- a/example/form-example.tsx
+++ b/example/form-example.tsx
@@ -2,8 +2,10 @@ import * as React from 'react';
 import Example from './example-block';
 
 import { Button, NumberBox } from '../src';
-import Form, { GroupItem, Item, RequiredRule, SimpleItem } from '../src/form';
-import TextArea from '../src/text-area';
+import {
+  Form, GroupItem, Item, RequiredRule, SimpleItem,
+} from '../src/form';
+import { TextArea } from '../src/text-area';
 
 const employee: any = {
   ID: 1,
@@ -27,8 +29,13 @@ const positions = [
   'Shipping Manager',
 ];
 
+const getTextBoxComponent = (disabled: boolean) => (props: any) => {
+  const { data } = props;
+  return <NumberBox disabled={disabled} value={data.editorOptions.value} />;
+};
+
 export default class extends React.Component<any, {disableIdInput: boolean}> {
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
 
     this.state = {
@@ -37,11 +44,11 @@ export default class extends React.Component<any, {disableIdInput: boolean}> {
   }
 
   private toggleIdInputState = () => {
-    const {disableIdInput} = this.state;
-    this.setState({disableIdInput: !disableIdInput});
+    const { disableIdInput } = this.state;
+    this.setState({ disableIdInput: !disableIdInput });
   };
 
-  public render() {
+  public render(): React.ReactNode {
     const { disableIdInput } = this.state;
     return (
       <Example title="DxForm">
@@ -92,8 +99,3 @@ export default class extends React.Component<any, {disableIdInput: boolean}> {
     );
   }
 }
-
-const getTextBoxComponent = (disabled: boolean) => (props: any) => {
-  const {data} = props;
-  return <NumberBox disabled={disabled} value={data.editorOptions.value} />;
-};
diff --git a/example/list-example.tsx b/example/list-example.tsx
index a4e4eae..13ff5ac 100644
--- a/example/list-example.tsx
+++ b/example/list-example.tsx
@@ -1,10 +1,11 @@
+/* eslint-disable max-classes-per-file */
 import * as React from 'react';
 
 import DataSource from 'devextreme/data/data_source';
 
-import Button from '../src/button';
-import List, { Item as ListItem } from '../src/list';
-import TextBox from '../src/text-box';
+import { Button } from '../src/button';
+import { List, Item as ListItem } from '../src/list';
+import { TextBox } from '../src/text-box';
 
 import Example from './example-block';
 
@@ -15,8 +16,13 @@ interface IListItemProps {
   index: number;
 }
 
-class Item extends React.Component<IListItemProps, { counter: number }> {
+const items: IListItemProps[] = [
+  { text: '123' },
+  { text: '234' },
+  { text: '567' },
+];
 
+class Item extends React.Component<IListItemProps, { counter: number }> {
   constructor(props: IListItemProps) {
     super(props);
     this.state = {
@@ -27,7 +33,7 @@ class Item extends React.Component<IListItemProps, { counter: number }> {
   }
 
   private handleClick() {
-    const {counter} = this.state;
+    const { counter } = this.state;
     this.setState({
       counter: counter + 1,
     });
@@ -35,9 +41,9 @@ class Item extends React.Component<IListItemProps, { counter: number }> {
 
   public render() {
     const { data: { text }, index } = this.props;
-    const {counter} = this.state;
+    const { counter } = this.state;
     return (
-      <i onClick={this.handleClick}>
+      <button type="button" onClick={this.handleClick}>
         {index + 1}
         . Component template for item
         {text}
@@ -46,7 +52,7 @@ class Item extends React.Component<IListItemProps, { counter: number }> {
           Clicks:
           {counter}
         </b>
-      </i>
+      </button>
     );
   }
 }
@@ -62,10 +68,9 @@ function ItemsRender(item: string) {
 const listItems: string[] = ['orange', 'apple', 'potato'];
 
 export default class extends React.Component<any, { text: string; items: IListItemProps[]; }> {
-
   private dataSource: DataSource;
 
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       text: '',
@@ -79,7 +84,7 @@ export default class extends React.Component<any, { text: string; items: IListIt
           data: items,
         },
         sort: [
-          { getter: 'text', desc: true},
+          { getter: 'text', desc: true },
         ],
         pageSize: 1,
       },
@@ -89,7 +94,7 @@ export default class extends React.Component<any, { text: string; items: IListIt
     this.addTextToList = this.addTextToList.bind(this);
   }
 
-  public componentWillUnmount() {
+  public componentWillUnmount(): void {
     this.dataSource.dispose();
   }
 
@@ -100,15 +105,15 @@ export default class extends React.Component<any, { text: string; items: IListIt
   }
 
   private addTextToList() {
-    const {items, text} = this.state;
+    const { items: stateItems, text } = this.state;
     this.setState({
-      items: [...items, { text }],
+      items: [...stateItems, { text }],
       text: '',
     });
   }
 
-  public render() {
-    const {items, text} = this.state;
+  public render(): React.ReactNode {
+    const { items: stateItems, text } = this.state;
     return (
       <Example title="DxList" state={this.state}>
         <hr />
@@ -128,7 +133,7 @@ export default class extends React.Component<any, { text: string; items: IListIt
         <h4>List with component template</h4>
         <List
           repaintChangesOnly
-          items={items}
+          items={stateItems}
           itemComponent={Item}
           itemKeyFn={ItemKeyGetter}
         />
@@ -143,9 +148,3 @@ export default class extends React.Component<any, { text: string; items: IListIt
     );
   }
 }
-
-const items: IListItemProps[] = [
-  { text: '123' },
-  { text: '234' },
-  { text: '567' },
-];
diff --git a/example/map-example.tsx b/example/map-example.tsx
index 976d149..d0d35a9 100644
--- a/example/map-example.tsx
+++ b/example/map-example.tsx
@@ -1,7 +1,10 @@
+/* eslint-disable react/no-unused-state */
 import * as React from 'react';
 import Example from './example-block';
 
-import Map, { Location, Marker, Route, Tooltip } from '../src/map';
+import {
+  Map, Location, Marker, Route, Tooltip,
+} from '../src/map';
 
 interface IPosition {
   lat: number;
@@ -14,8 +17,7 @@ const startPos = {
 };
 
 export default class extends React.Component<any, { text: string; pos: IPosition }> {
-
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     const pos = { ...startPos };
     this.state = {
@@ -26,8 +28,8 @@ export default class extends React.Component<any, { text: string; pos: IPosition
     this.updatePos = this.updatePos.bind(this);
   }
 
-  public updatePos() {
-    const {pos: {lat, lng}} = this.state;
+  public updatePos(): void {
+    const { pos: { lat, lng } } = this.state;
     const pos = {
       lat: lat + 0.01,
       lng: lng + 0.05,
@@ -39,8 +41,8 @@ export default class extends React.Component<any, { text: string; pos: IPosition
     });
   }
 
-  public render() {
-    const {pos} = this.state;
+  public render(): React.ReactNode {
+    const { pos } = this.state;
     return (
       <Example title="dxMap" state={this.state}>
         <button type="button" onClick={this.updatePos}>Move!</button>
diff --git a/example/popup-example.tsx b/example/popup-example.tsx
index 4266804..5bba114 100644
--- a/example/popup-example.tsx
+++ b/example/popup-example.tsx
@@ -1,21 +1,20 @@
 import * as React from 'react';
 
-import Button from '../src/button';
-import Popup from '../src/popup';
-import ScrollView from '../src/scroll-view';
-import TextBox from '../src/text-box';
+import { Button } from '../src/button';
+import { Popup } from '../src/popup';
+import { ScrollView } from '../src/scroll-view';
+import { TextBox } from '../src/text-box';
 import Example from './example-block';
 
 const VALID_TEXT = 'good';
 const validateText = (text: string) => text === VALID_TEXT;
 
 export default class extends React.Component<any, { visible: boolean; text: string; }> {
-
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       visible: false,
-      text: 'non-' + VALID_TEXT,
+      text: `non-${VALID_TEXT}`,
     };
 
     this.toggle = this.toggle.bind(this);
@@ -23,7 +22,7 @@ export default class extends React.Component<any, { visible: boolean; text: stri
   }
 
   private toggle(visible: boolean) {
-    const {text} = this.state;
+    const { text } = this.state;
     this.setState({
       visible: visible || !validateText(text),
     });
@@ -35,8 +34,8 @@ export default class extends React.Component<any, { visible: boolean; text: stri
     });
   }
 
-  public render() {
-    const {text, visible} = this.state;
+  public render(): React.ReactNode {
+    const { text, visible } = this.state;
     return (
       <Example title="DxPopup" state={this.state}>
         <TextBox value={text} onValueChanged={this.handleTextUpdate} valueChangeEvent="input" />
@@ -49,7 +48,7 @@ export default class extends React.Component<any, { visible: boolean; text: stri
           width={600}
           height={400}
         >
-          Enter valid text (it should be 'good'):
+          Enter valid text (it should be `&apos;`good`&apos;`):
           <br />
           <TextBox value={text} onValueChanged={this.handleTextUpdate} valueChangeEvent="input" />
           <br />
diff --git a/example/scheduler-example.tsx b/example/scheduler-example.tsx
index e81b512..b349522 100644
--- a/example/scheduler-example.tsx
+++ b/example/scheduler-example.tsx
@@ -6,8 +6,8 @@ import { Scheduler } from '../src/scheduler';
 import { appointments } from './data';
 
 class DateCell extends React.PureComponent<any> {
-  public render() {
-    const {data} =  this.props;
+  public render(): React.ReactNode {
+    const { data } = this.props;
     const { date: now, text } = data;
     const start = new Date(now.getFullYear(), 0, 0).getTime();
     const dayNumber = Math.floor((now - start) / (1000 * 60 * 60 * 24));
@@ -20,19 +20,15 @@ class DateCell extends React.PureComponent<any> {
   }
 }
 
-export default class extends React.Component<any, any> {
-  public render() {
-    return (
-      <Example title="DxScheduler">
-        <Scheduler
-          dateCellComponent={DateCell}
-          dataSource={appointments}
-          height={400}
-          startDayHour={9}
-          defaultCurrentView="week"
-          defaultCurrentDate={new Date(2017, 4, 25)}
-        />
-      </Example>
-    );
-  }
-}
+export default (): React.ReactElement | null => (
+  <Example title="DxScheduler">
+    <Scheduler
+      dateCellComponent={DateCell}
+      dataSource={appointments}
+      height={400}
+      startDayHour={9}
+      defaultCurrentView="week"
+      defaultCurrentDate={new Date(2017, 4, 25)}
+    />
+  </Example>
+);
diff --git a/example/scroll-view-example.tsx b/example/scroll-view-example.tsx
index fb9bdad..4e611d0 100644
--- a/example/scroll-view-example.tsx
+++ b/example/scroll-view-example.tsx
@@ -1,14 +1,15 @@
+/* eslint-disable no-alert */
+/* eslint-disable max-len */
 import * as React from 'react';
 
 import Example from './example-block';
 
-import Button from '../src/button';
-import ScrollView from '../src/scroll-view';
-import TextBox from '../src/text-box';
+import { Button } from '../src/button';
+import { ScrollView } from '../src/scroll-view';
+import { TextBox } from '../src/text-box';
 
 export default class extends React.Component<any, { text: string }> {
-
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       text: 'Clear me',
@@ -30,8 +31,8 @@ export default class extends React.Component<any, { text: string }> {
     });
   }
 
-  public render() {
-    const {text} = this.state;
+  public render(): React.ReactNode {
+    const { text } = this.state;
     return (
       <Example title="DxScrollView" state={this.state}>
         <ScrollView height="150px">
diff --git a/example/selectbox-example.tsx b/example/selectbox-example.tsx
index 3f382d8..f822d00 100644
--- a/example/selectbox-example.tsx
+++ b/example/selectbox-example.tsx
@@ -1,29 +1,7 @@
 import * as React from 'react';
 import { SelectBox } from '../src/select-box';
 
-const SelectBoxItem = (option: any) => {
-  return <span>{option.data.value}</span>;
-};
-
-class SelectBoxEditor extends React.Component {
-
-  public render() {
-    return (
-      <SelectBox
-        searchEnabled
-        showClearButton
-        searchTimeout={0}
-        noDataText="No results found"
-        displayExpr="label"
-        valueExpr="value"
-        items={items}
-        itemComponent={SelectBoxItem}
-      />
-    );
-  }
-}
-
-export default SelectBoxEditor;
+const SelectBoxItem = (option: any) => <span>{option.data.value}</span>;
 
 const items = [
   {
@@ -47,3 +25,16 @@ const items = [
     value: '5',
   },
 ];
+
+export default (): React.ReactElement | null => (
+  <SelectBox
+    searchEnabled
+    showClearButton
+    searchTimeout={0}
+    noDataText="No results found"
+    displayExpr="label"
+    valueExpr="value"
+    items={items}
+    itemComponent={SelectBoxItem}
+  />
+);
diff --git a/example/slide-out-view-example.tsx b/example/slide-out-view-example.tsx
index 16a5fdb..d57e7cb 100644
--- a/example/slide-out-view-example.tsx
+++ b/example/slide-out-view-example.tsx
@@ -1,7 +1,9 @@
 import * as React from 'react';
 import Example from './example-block';
 
-import { Button, SlideOutView, Template, Toolbar } from '../src';
+import {
+  Button, SlideOutView, Template, Toolbar,
+} from '../src';
 
 function renderMenuTemplate() {
   const items = [{
@@ -27,8 +29,7 @@ function renderMenuTemplate() {
 }
 
 export default class extends React.Component<any, any> {
-
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       menuVisible: true,
@@ -37,8 +38,8 @@ export default class extends React.Component<any, any> {
     this.toggle = this.toggle.bind(this);
   }
 
-  public toggle() {
-    const {menuVisible} = this.state;
+  public toggle(): void {
+    const { menuVisible } = this.state;
     this.setState({ menuVisible: !menuVisible });
   }
 
@@ -48,8 +49,8 @@ export default class extends React.Component<any, any> {
     }
   }
 
-  public render() {
-    const {menuVisible} = this.state;
+  public render(): React.ReactNode {
+    const { menuVisible } = this.state;
     return (
       <Example title="DxSlideOutView" state={this.state}>
         <Button text="toggle" onClick={this.toggle} />
diff --git a/example/standalone-validator.tsx b/example/standalone-validator.tsx
index 9b98191..2ed02b0 100644
--- a/example/standalone-validator.tsx
+++ b/example/standalone-validator.tsx
@@ -1,5 +1,7 @@
 import * as React from 'react';
-import { Button, TextBox, ValidationSummary, Validator } from '../src';
+import {
+  Button, TextBox, ValidationSummary, Validator,
+} from '../src';
 
 import { RequiredRule } from '../src/validator';
 import Example from './example-block';
@@ -11,16 +13,14 @@ class ValidatorExample extends React.Component<any, any> {
 
   private _callbacks: any[] = [];
 
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       isValid: true,
     };
 
     this._adapter = {
-      getValue: () => {
-        return this._currentValue;
-      },
+      getValue: () => this._currentValue,
       validationRequestsCallbacks: this._callbacks,
       applyValidationResults: (e: any) => {
         this.setState({ isValid: e.isValid });
@@ -40,8 +40,8 @@ class ValidatorExample extends React.Component<any, any> {
     e.validationGroup.validate();
   };
 
-  public render() {
-    const {isValid} = this.state;
+  public render(): React.ReactNode {
+    const { isValid } = this.state;
     return (
       <div>
         <Example title="Standalone Validator example">
diff --git a/example/text-box-example.tsx b/example/text-box-example.tsx
index 39f9195..90b9653 100644
--- a/example/text-box-example.tsx
+++ b/example/text-box-example.tsx
@@ -1,16 +1,16 @@
+/* eslint-disable react/no-unused-state */
 import * as React from 'react';
+import dxTextBox from 'devextreme/ui/text_box';
 import Example from './example-block';
 
-import dxTextBox from 'devextreme/ui/text_box';
 import { Button } from '../src/button';
 import { TextBox } from '../src/text-box';
 import { RequiredRule, Validator } from '../src/validator';
 
 export default class extends React.Component<any, { text: string; uncontrolledText: string; }> {
-
   private textBox: dxTextBox;
 
-  constructor(props: any) {
+  constructor(props: unknown) {
     super(props);
     this.state = {
       text: 'text',
@@ -28,24 +28,28 @@ export default class extends React.Component<any, { text: string; uncontrolledTe
 
   private updateUncontrolledValue() {
     this.setState({
-      uncontrolledText: '#' + this.textBox.option('value'),
+      uncontrolledText: `#${this.textBox.option('value')}`,
     });
   }
 
   private handleChange(e: any) {
     this.setState({
-      text: '#' + (e.value as string).toUpperCase().replace('A', '_'),
+      text: `#${(e.value as string).toUpperCase().replace('A', '_')}`,
     });
   }
 
-  public render() {
-    const {text} = this.state;
+  public render(): React.ReactNode {
+    const { text } = this.state;
     return (
       <Example title="DxTextBox" state={this.state}>
         uncontrolled mode
         <TextBox
           defaultValue="initial text"
-          ref={(ref) => ref && (this.textBox = ref.instance)}
+          ref={(ref) => {
+            if (ref) {
+              this.textBox = ref.instance;
+            }
+          }}
         />
         <br />
         <Button onClick={this.setFocusToTextBox} text="Set focus" />
diff --git a/example/toolbar-example.tsx b/example/toolbar-example.tsx
index 69d5d88..7bf7de4 100644
--- a/example/toolbar-example.tsx
+++ b/example/toolbar-example.tsx
@@ -4,16 +4,14 @@ import Example from './example-block';
 import { Button } from '../src/button';
 import { Toolbar } from '../src/toolbar';
 
-const ItemComponent  = (data: {data: {text: string}}) => {
-  return (
-    <Button text={data.data.text} />
-  );
-};
+const ItemComponent = (data: {data: {text: string}}) => (
+  <Button text={data.data.text} />
+);
 
-const items = [{text: 'Text'}, {text: 'Text2'}];
+const items = [{ text: 'Text' }, { text: 'Text2' }];
 
 export default class extends React.Component<any, any> {
-  public render() {
+  public render(): React.ReactNode {
     return (
       <Example title="Toolbar" state={this.state}>
         <Toolbar items={items} itemComponent={ItemComponent} />
diff --git a/example/validation-example.tsx b/example/validation-example.tsx
index 9ac96dd..78b4fb8 100644
--- a/example/validation-example.tsx
+++ b/example/validation-example.tsx
@@ -7,23 +7,16 @@ import { ValidationGroup } from '../src/validation-group';
 import { ValidationSummary } from '../src/validation-summary';
 import { EmailRule, RequiredRule, Validator } from '../src/validator';
 
-export default class extends React.Component<any, any> {
-
-  constructor(props: any) {
-    super(props);
-
-    this.validate = this.validate.bind(this);
+const validate = (params: any): void => {
+  const result = params.validationGroup.validate();
+  if (result.isValid) {
+    // form data is valid
+    params.validationGroup.reset();
   }
+};
 
-  private validate(params: any) {
-    const result = params.validationGroup.validate();
-    if (result.isValid) {
-      // form data is valid
-      params.validationGroup.reset();
-    }
-  }
-
-  public render() {
+export default class extends React.Component<any, any> {
+  public render(): React.ReactNode {
     return (
       <Example title="Validation" state={this.state}>
         <ValidationGroup>
@@ -43,7 +36,7 @@ export default class extends React.Component<any, any> {
           <br />
           <Button
             text="Submit"
-            onClick={this.validate}
+            onClick={validate}
           />
         </ValidationGroup>
       </Example>
diff --git a/package-lock.json b/package-lock.json
index 67a6858..9576ead 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2816,8 +2816,7 @@
     "dasherize": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz",
-      "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=",
-      "dev": true
+      "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg="
     },
     "data-urls": {
       "version": "2.0.0",
@@ -3194,8 +3193,7 @@
     "dot": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/dot/-/dot-1.1.3.tgz",
-      "integrity": "sha512-/nt74Rm+PcfnirXGEdhZleTwGC2LMnuKTeeTIlI82xb5loBBoXNYzr2ezCroPSMtilK8EZIfcNZwOcHN+ib1Lg==",
-      "dev": true
+      "integrity": "sha512-/nt74Rm+PcfnirXGEdhZleTwGC2LMnuKTeeTIlI82xb5loBBoXNYzr2ezCroPSMtilK8EZIfcNZwOcHN+ib1Lg=="
     },
     "duplexify": {
       "version": "3.7.1",
@@ -12830,9 +12828,9 @@
       }
     },
     "websocket-extensions": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
-      "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+      "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
       "dev": true
     },
     "whatwg-encoding": {
diff --git a/package.json b/package.json
index add7011..be790a6 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,9 @@
     "eslint-plugin-jest-formatting": "^2.0.0",
     "eslint-plugin-qunit": "^5.0.0",
     "eslint-plugin-spellcheck": "0.0.17",
-    "prop-types": "^15.6.1"
+    "prop-types": "^15.6.1",
+    "dasherize": "^2.0.0",
+    "dot": "^1.1.2"
   },
   "peerDependencies": {
     "devextreme": "~20.1.7",
@@ -34,11 +36,9 @@
     "@typescript-eslint/eslint-plugin": "^4.4.1",
     "@typescript-eslint/parser": "^4.4.1",
     "css-loader": "^2.1.0",
-    "dasherize": "^2.0.0",
     "del": "^3.0.0",
     "devextreme": "~20.1.7",
     "devextreme-internal-tools": "stable",
-    "dot": "^1.1.2",
     "enzyme": "^3.7.0",
     "enzyme-adapter-react-16": "^1.6.0",
     "eslint": "^7.11.0",
@@ -55,6 +55,8 @@
     "gulp-tslint": "^8.1.4",
     "gulp-typescript": "^5.0.0",
     "hoek": ">=5.0.3",
+    "istanbul": "^0.4.5",
+    "istanbul-cobertura-badger": "^1.3.1",
     "jest": "^24.8.0",
     "merge": ">=1.2.1",
     "minimatch": ">=3.0.2",
@@ -76,10 +78,10 @@
     "start": "npm run clean && npm run generate && npm run serve",
     "serve": "webpack-dev-server",
     "lint": "gulp lint",
-    "build": "npm run clean && npm run lint && npm run generate",
+    "build": "npm run clean && npm run generate && npm run lint",
     "generate": "gulp generate",
     "clean": "gulp clean",
-    "test": "jest",
+    "test": "jest --coverage",
     "test-watch": "jest --watch",
     "pack": "gulp npm.pack",
     "update-integration-meta": "dx-tools update-integration-meta --output-path ./tools/integration-data.json"
diff --git a/shippable.yml b/shippable.yml
index c3c3347..b943281 100644
--- a/shippable.yml
+++ b/shippable.yml
@@ -9,12 +9,16 @@ build:
     image_name: drydock/u14nod
     image_tag: prod
     pull: true
-
+  env:
+    - XUNIT_FILE=coverage/clover.xml
   ci:
     - npm install
     - npm run lint
     - npm run pack
     - npm run test
+  post_ci:
+    - ./node_modules/.bin/istanbul report cobertura --dir shippable/codecoverage/
+    - ./node_modules/.bin/istanbul check-coverage --statements 90 --functions 90 --lines 90 --branches 87
   cache: true
   cache_dir_list:
     - $SHIPPABLE_BUILD_DIR/node_modules
\ No newline at end of file
diff --git a/src/core/component-base.ts b/src/core/component-base.ts
index 08f5388..fa337e7 100644
--- a/src/core/component-base.ts
+++ b/src/core/component-base.ts
@@ -41,6 +41,8 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
 
   private _optionsManager: OptionsManager;
 
+  private _useDeferUpdateForTemplates = false;
+
   constructor(props: P) {
     super(props);
 
@@ -49,28 +51,28 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
 
     this._templatesStore = new TemplatesStore(() => {
       if (this._templatesRendererRef) {
-        this._templatesRendererRef.scheduleUpdate();
+        this._templatesRendererRef.scheduleUpdate(this._useDeferUpdateForTemplates);
       }
     });
     this._templatesManager = new TemplatesManager(this._templatesStore);
     this._optionsManager = new OptionsManager(this._templatesManager);
   }
 
-  public componentDidMount() {
+  public componentDidMount(): void {
     this._updateCssClasses(null, this.props);
   }
 
-  public componentDidUpdate(prevProps: P) {
+  public componentDidUpdate(prevProps: P): void {
     this._updateCssClasses(prevProps, this.props);
 
     const config = this._getConfig();
     this._optionsManager.update(config);
     if (this._templatesRendererRef) {
-      this._templatesRendererRef.scheduleUpdate();
+      this._templatesRendererRef.scheduleUpdate(this._useDeferUpdateForTemplates);
     }
   }
 
-  public componentWillUnmount() {
+  public componentWillUnmount(): void {
     if (this._instance) {
       events.triggerHandler(this._element, DX_REMOVE_EVENT);
       this._instance.dispose();
@@ -78,7 +80,7 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
     this._optionsManager.dispose();
   }
 
-  protected _createWidget(element?: Element) {
+  protected _createWidget(element?: Element): void {
     element = element || this._element;
 
     const config = this._getConfig();
@@ -90,6 +92,7 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
       },
     );
 
+    this._useDeferUpdateForTemplates = this._instance.option('integrationOptions.useDeferUpdateForTemplates');
     this._optionsManager.setInstance(this._instance, config);
     this._instance.on('optionChanged', this._optionsManager.onOptionChanged);
   }
@@ -112,11 +115,11 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
 
   private _getElementProps(): Record<string, any> {
     const elementProps: Record<string, any> = {
-      ref: (element: HTMLDivElement) => this._element = element,
+      ref: (element: HTMLDivElement) => { this._element = element; },
     };
 
     elementPropNames.forEach((name) => {
-      const props = this.props;
+      const { props } = this;
       if (name in props) {
         elementProps[name] = props[name];
       }
@@ -145,12 +148,12 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
     }
   }
 
-  protected renderChildren() {
-    const {children} = this.props;
+  protected renderChildren(): React.ReactNode {
+    const { children } = this.props;
     return children;
   }
 
-  public render() {
+  public render(): React.ReactNode {
     return React.createElement(
       'div',
       this._getElementProps(),
@@ -164,7 +167,6 @@ abstract class ComponentBase<P extends IHtmlOptions> extends React.PureComponent
       ),
     );
   }
-
 }
 
 export {
diff --git a/src/core/component.ts b/src/core/component.ts
index 3d8d74b..0e342e6 100644
--- a/src/core/component.ts
+++ b/src/core/component.ts
@@ -12,20 +12,20 @@ class Component<P> extends ComponentBase<P> {
     this._registerExtension = this._registerExtension.bind(this);
   }
 
-  public componentDidMount() {
+  public componentDidMount(): void {
     super.componentDidMount();
     this._createWidget();
     this._createExtensions();
   }
 
-  protected renderChildren() {
+  protected renderChildren(): Record<string, unknown>[]|null| undefined {
     return React.Children.map(
       this.props.children,
       (child) => {
-        if (child && ExtensionComponent.isPrototypeOf((child as any).type)) {
+        if (child && Object.prototype.isPrototypeOf.call(ExtensionComponent, (child as any).type)) {
           return React.cloneElement(
             child as any,
-            { onMounted: this._registerExtension},
+            { onMounted: this._registerExtension },
           );
         }
 
diff --git a/src/core/configuration/comparer.ts b/src/core/configuration/comparer.ts
index 90b8a61..a422c80 100644
--- a/src/core/configuration/comparer.ts
+++ b/src/core/configuration/comparer.ts
@@ -6,22 +6,40 @@ interface IConfigChanges {
   options: Record<string, any>;
   removedOptions: string[];
   templates: Record<string, ITemplate>;
-  addRemovedValues(currentOptions: Record<string, any>, prevOptions: Record<string, any>, path: string): void;
+  addRemovedValues(
+    currentOptions: Record<string, any>, prevOptions: Record<string, any>, path: string): void;
 }
 
-function getChanges(current: IConfigNode, prev: IConfigNode) {
-  const changesAccum: IConfigChanges = {
-    options: {},
-    removedOptions: [],
-    templates: {},
-    addRemovedValues(currentOptions, prevOptions, path) {
-      appendRemovedValues(currentOptions, prevOptions, path, this.removedOptions);
-    },
-  };
+function compareTemplates(current: IConfigNode, prev: IConfigNode, changesAccum: IConfigChanges) {
+  const currentTemplatesOptions: Record<string, any> = {};
+  const currentTemplates: Record<string, ITemplate> = {};
+  const prevTemplatesOptions: Record<string, any> = {};
+  const prevTemplates: Record<string, ITemplate> = {};
 
-  compare(current, prev, changesAccum);
+  buildTemplates(current, currentTemplatesOptions, currentTemplates);
+  buildTemplates(prev, prevTemplatesOptions, prevTemplates);
 
-  return changesAccum;
+  changesAccum.addRemovedValues(currentTemplatesOptions, prevTemplatesOptions, current.fullName);
+  // TODO: support switching to default templates
+  // appendRemovedValues(currentTemplates, prevTemplates, "", changesAccum.templates);
+
+  Object.keys(currentTemplatesOptions).forEach((key) => {
+    if (currentTemplatesOptions[key] === prevTemplatesOptions[key]) {
+      return;
+    }
+
+    changesAccum.options[mergeNameParts(current.fullName, key)] = currentTemplatesOptions[key];
+  });
+
+  Object.keys(currentTemplates).forEach((key) => {
+    const currentTemplate = currentTemplates[key];
+    const prevTemplate = prevTemplates[key];
+    if (prevTemplate && currentTemplate.content === prevTemplate.content) {
+      return;
+    }
+
+    changesAccum.templates[key] = currentTemplate;
+  });
 }
 
 function compare(current: IConfigNode, prev: IConfigNode, changesAccum: IConfigChanges) {
@@ -38,58 +56,28 @@ function compare(current: IConfigNode, prev: IConfigNode, changesAccum: IConfigC
   changesAccum.addRemovedValues(
     current.configCollections,
     prev.configCollections,
-    current.fullName);
+    current.fullName,
+  );
   changesAccum.addRemovedValues(current.configs, prev.configs, current.fullName);
 
+  // eslint-disable-next-line @typescript-eslint/no-use-before-define
   compareCollections(current, prev, changesAccum);
 
-  for (const key of Object.keys(current.configs)) {
+  Object.keys(current.configs).forEach((key) => {
     compare(current.configs[key], prev.configs[key], changesAccum);
-  }
+  });
 
-  for (const key of Object.keys(current.options)) {
+  Object.keys(current.options).forEach((key) => {
     if (current.options[key] === prev.options[key]) {
-      continue;
+      return;
     }
 
     changesAccum.options[mergeNameParts(current.fullName, key)] = current.options[key];
-  }
+  });
 
   compareTemplates(current, prev, changesAccum);
 }
 
-function compareTemplates(current: IConfigNode, prev: IConfigNode, changesAccum: IConfigChanges) {
-  const currentTemplatesOptions: Record<string, any> = {};
-  const currentTemplates: Record<string, ITemplate> = {};
-  const prevTemplatesOptions: Record<string, any> = {};
-  const prevTemplates: Record<string, ITemplate> = {};
-
-  buildTemplates(current, currentTemplatesOptions, currentTemplates);
-  buildTemplates(prev, prevTemplatesOptions, prevTemplates);
-
-  changesAccum.addRemovedValues(currentTemplatesOptions, prevTemplatesOptions, current.fullName);
-  // TODO: support switching to default templates
-  // appendRemovedValues(currentTemplates, prevTemplates, "", changesAccum.templates);
-
-  for (const key of Object.keys(currentTemplatesOptions)) {
-    if (currentTemplatesOptions[key] === prevTemplatesOptions[key]) {
-      continue;
-    }
-
-    changesAccum.options[mergeNameParts(current.fullName, key)] = currentTemplatesOptions[key];
-  }
-
-  for (const key of Object.keys(currentTemplates)) {
-    const currentTemplate = currentTemplates[key];
-    const prevTemplate = prevTemplates[key];
-    if (prevTemplate && currentTemplate.content === prevTemplate.content) {
-      continue;
-    }
-
-    changesAccum.templates[key] = currentTemplate;
-  }
-}
-
 function appendRemovedValues(
   current: Record<string, any>,
   prev: Record<string, any>,
@@ -98,9 +86,24 @@ function appendRemovedValues(
 ) {
   const removedKeys = Object.keys(prev).filter((key) => Object.keys(current).indexOf(key) < 0);
 
-  for (const key of removedKeys) {
+  removedKeys.forEach((key) => {
     changesAccum.push(mergeNameParts(path, key));
-  }
+  });
+}
+
+function getChanges(current: IConfigNode, prev: IConfigNode): IConfigChanges {
+  const changesAccum: IConfigChanges = {
+    options: {},
+    removedOptions: [],
+    templates: {},
+    addRemovedValues(currentOptions, prevOptions, path) {
+      appendRemovedValues(currentOptions, prevOptions, path, this.removedOptions);
+    },
+  };
+
+  compare(current, prev, changesAccum);
+
+  return changesAccum;
 }
 
 function compareCollections(
@@ -108,25 +111,25 @@ function compareCollections(
   prev: IConfigNode,
   changesAccum: IConfigChanges,
 ) {
-  for (const key of Object.keys(current.configCollections)) {
+  Object.keys(current.configCollections).forEach((key) => {
     const currentCollection = current.configCollections[key];
     const prevCollection = prev.configCollections[key] || [];
     if (!currentCollection || currentCollection.length !== prevCollection.length) {
       const updatedCollection: Array<Record<string, any>> = [];
-      currentCollection.map(
+      currentCollection.forEach(
         (item) => {
           const config = buildNode(item, changesAccum.templates, true);
           updatedCollection.push(config);
         },
       );
       changesAccum.options[mergeNameParts(current.fullName, key)] = updatedCollection;
-      continue;
+      return;
     }
 
-    for (let i = 0; i < currentCollection.length; i++) {
+    for (let i = 0; i < currentCollection.length; i += 1) {
       compare(currentCollection[i], prevCollection[i], changesAccum);
     }
-  }
+  });
 }
 
 export {
diff --git a/src/core/configuration/react/element.ts b/src/core/configuration/react/element.ts
index 06e5257..34ed9e6 100644
--- a/src/core/configuration/react/element.ts
+++ b/src/core/configuration/react/element.ts
@@ -1,4 +1,4 @@
-import { ITemplateMeta, Template } from '../../template';
+import { ITemplateMeta, Template as TemplateComp } from '../../template';
 
 enum ElementType {
   Option,
@@ -48,7 +48,7 @@ function getElementInfo(
     };
   }
 
-  if (reactElement.type === Template) {
+  if (reactElement.type === TemplateComp) {
     return {
       type: ElementType.Template,
       props: reactElement.props,
@@ -59,7 +59,7 @@ function getElementInfo(
 
   if (elementDescriptor.OptionName) {
     let name = elementDescriptor.OptionName;
-    let isCollectionItem =  elementDescriptor.IsCollectionItem;
+    let isCollectionItem = elementDescriptor.IsCollectionItem;
 
     const expectation = parentExpectedChildren && parentExpectedChildren[name];
     if (expectation) {
diff --git a/src/core/configuration/react/tree.ts b/src/core/configuration/react/tree.ts
index 3a35026..161aaf1 100644
--- a/src/core/configuration/react/tree.ts
+++ b/src/core/configuration/react/tree.ts
@@ -3,7 +3,9 @@ import * as React from 'react';
 import { ITemplateMeta, ITemplateProps } from '../../template';
 import { separateProps } from '../../widget-config';
 
-import { ElementType, getElementInfo, IExpectedChild, IOptionElement } from './element';
+import {
+  ElementType, getElementInfo, IExpectedChild, IOptionElement,
+} from './element';
 
 import { IConfigNode, ITemplate } from '../config-node';
 import { mergeNameParts } from '../utils';
@@ -16,56 +18,6 @@ interface IWidgetDescriptor {
   expectedChildren: Record<string, IExpectedChild>;
 }
 
-function buildConfigTree(widgetDescriptor: IWidgetDescriptor, props: Record<string, any>) {
-  return createConfigNode(
-    {
-      type: ElementType.Option,
-      descriptor: {
-        name: '',
-        isCollection: false,
-        ...widgetDescriptor,
-      },
-      props,
-    },
-    '',
-  );
-}
-
-function createConfigNode(element: IOptionElement, path: string): IConfigNode {
-  const fullName = element.descriptor.isCollection
-    ? path
-    : mergeNameParts(path, element.descriptor.name);
-
-  const separatedValues = separateProps(
-    element.props,
-    element.descriptor.initialValuesProps,
-    element.descriptor.templates,
-  );
-
-  const childrenData = processChildren(element, fullName);
-
-  for (const templateMeta of element.descriptor.templates) {
-    const template = getAnonymousTemplate(
-      element.props,
-      templateMeta,
-      path.length > 0 ? childrenData.hasTranscludedContent : false,
-    );
-    if (template) {
-      childrenData.templates.push(template);
-    }
-  }
-
-  return {
-    fullName,
-    predefinedOptions: element.descriptor.predefinedValuesProps,
-    initialOptions: separatedValues.defaults,
-    options: separatedValues.options,
-    templates: childrenData.templates,
-    configCollections: childrenData.configCollections,
-    configs: childrenData.configs,
-  };
-}
-
 function processChildren(parentElement: IOptionElement, parentFullName: string) {
   const templates: ITemplate[] = [];
   const configCollections: Record<string, IConfigNode[]> = {};
@@ -97,18 +49,20 @@ function processChildren(parentElement: IOptionElement, parentFullName: string)
           configCollections[element.descriptor.name] = collection;
         }
 
+        // eslint-disable-next-line @typescript-eslint/no-use-before-define
         const collectionItem = createConfigNode(
           element,
-          mergeNameParts(
+          `${mergeNameParts(
             parentFullName,
             element.descriptor.name,
-          ) + '[' + collection.length + ']',
+          )}[${collection.length}]`,
         );
 
         collection.push(collectionItem);
         return;
       }
 
+      // eslint-disable-next-line @typescript-eslint/no-use-before-define
       const configNode = createConfigNode(
         element,
         parentFullName,
@@ -126,6 +80,59 @@ function processChildren(parentElement: IOptionElement, parentFullName: string)
   };
 }
 
+function createConfigNode(element: IOptionElement, path: string): IConfigNode {
+  const fullName = element.descriptor.isCollection
+    ? path
+    : mergeNameParts(path, element.descriptor.name);
+
+  const separatedValues = separateProps(
+    element.props,
+    element.descriptor.initialValuesProps,
+    element.descriptor.templates,
+  );
+
+  const childrenData = processChildren(element, fullName);
+
+  element.descriptor.templates.forEach((templateMeta) => {
+    const template = getAnonymousTemplate(
+      element.props,
+      templateMeta,
+      path.length > 0 ? childrenData.hasTranscludedContent : false,
+    );
+    if (template) {
+      childrenData.templates.push(template);
+    }
+  });
+
+  return {
+    fullName,
+    predefinedOptions: element.descriptor.predefinedValuesProps,
+    initialOptions: separatedValues.defaults,
+    options: separatedValues.options,
+    templates: childrenData.templates,
+    configCollections: childrenData.configCollections,
+    configs: childrenData.configs,
+  };
+}
+
+function buildConfigTree(
+  widgetDescriptor: IWidgetDescriptor,
+  props: Record<string, any>,
+): IConfigNode {
+  return createConfigNode(
+    {
+      type: ElementType.Option,
+      descriptor: {
+        name: '',
+        isCollection: false,
+        ...widgetDescriptor,
+      },
+      props,
+    },
+    '',
+  );
+}
+
 export {
   buildConfigTree,
 };
diff --git a/src/core/configuration/tree.ts b/src/core/configuration/tree.ts
index dc7ffc5..9f74b7c 100644
--- a/src/core/configuration/tree.ts
+++ b/src/core/configuration/tree.ts
@@ -6,14 +6,22 @@ interface IConfig {
   templates: Record<string, ITemplate>;
 }
 
-function buildConfig(root: IConfigNode, ignoreInitialValues: boolean): IConfig {
-  const templatesAccum: Record<string, ITemplate> = {};
-  const options = buildNode(root, templatesAccum, ignoreInitialValues);
-
-  return {
-    templates: templatesAccum,
-    options,
-  };
+function buildTemplates(
+  node: IConfigNode,
+  optionsAccum: Record<string, any>,
+  templatesAccum: Record<string, ITemplate>,
+): void {
+  node.templates.forEach(
+    (template) => {
+      if (template.isAnonymous) {
+        const templateName = mergeNameParts(node.fullName, template.optionName);
+        optionsAccum[template.optionName] = templateName;
+        templatesAccum[templateName] = template;
+      } else {
+        templatesAccum[template.optionName] = template;
+      }
+    },
+  );
 }
 
 function buildNode(
@@ -23,51 +31,43 @@ function buildNode(
 ): Record<string, any> {
   const result: Record<string, any> = {};
 
-  for (const key of Object.keys(node.predefinedOptions)) {
+  Object.keys(node.predefinedOptions).forEach((key) => {
     result[key] = node.predefinedOptions[key];
-  }
+  });
 
-  for (const key of Object.keys(node.configs)) {
+  Object.keys(node.configs).forEach((key) => {
     result[key] = buildNode(node.configs[key], templatesAccum, ignoreInitialValues);
-  }
+  });
 
-  for (const key of Object.keys(node.configCollections)) {
+  Object.keys(node.configCollections).forEach((key) => {
     result[key] = node.configCollections[key].map(
       (item) => buildNode(item, templatesAccum, ignoreInitialValues),
     );
-  }
+  });
 
   if (!ignoreInitialValues) {
-    for (const key of Object.keys(node.initialOptions)) {
+    Object.keys(node.initialOptions).forEach((key) => {
       result[key] = node.initialOptions[key];
-    }
+    });
   }
 
-  for (const key of Object.keys(node.options)) {
+  Object.keys(node.options).forEach((key) => {
     result[key] = node.options[key];
-  }
+  });
 
   buildTemplates(node, result, templatesAccum);
 
   return result;
 }
 
-function buildTemplates(
-  node: IConfigNode,
-  optionsAccum: Record<string, any>,
-  templatesAccum: Record<string, ITemplate>,
-) {
-  node.templates.map(
-    (template) => {
-      if (template.isAnonymous) {
-        const templateName = mergeNameParts(node.fullName, template.optionName);
-        optionsAccum[template.optionName] = templateName;
-        templatesAccum[templateName] = template;
-      } else {
-        templatesAccum[template.optionName] = template;
-      }
-    },
-  );
+function buildConfig(root: IConfigNode, ignoreInitialValues: boolean): IConfig {
+  const templatesAccum: Record<string, ITemplate> = {};
+  const options = buildNode(root, templatesAccum, ignoreInitialValues);
+
+  return {
+    templates: templatesAccum,
+    options,
+  };
 }
 
 interface IValueDescriptor {
@@ -81,6 +81,22 @@ enum ValueType {
   Array,
 }
 
+function findValueInObject(obj: any, path: string[]): undefined | IValueDescriptor {
+  const key = path.shift();
+  if (!key) {
+    return {
+      value: obj,
+      type: ValueType.Simple,
+    };
+  }
+
+  if (Object.keys(obj).includes(key)) {
+    return findValueInObject(obj[key], path);
+  }
+
+  return undefined;
+}
+
 function findValue(node: IConfigNode, path: string[]): undefined | IValueDescriptor {
   const name = path.shift();
 
@@ -103,12 +119,12 @@ function findValue(node: IConfigNode, path: string[]): undefined | IValueDescrip
   if (optionInfo.isCollectionItem) {
     const collection = node.configCollections[optionInfo.name];
     if (!collection) {
-      return;
+      return undefined;
     }
 
     const item = collection[optionInfo.index];
     if (!item) {
-      return;
+      return undefined;
     }
 
     return findValue(item, path);
@@ -122,7 +138,7 @@ function findValue(node: IConfigNode, path: string[]): undefined | IValueDescrip
   const childCollection = node.configCollections[optionInfo.name];
   if (childCollection) {
     if (path.length !== 0) {
-      return;
+      return undefined;
     }
 
     return {
@@ -131,23 +147,7 @@ function findValue(node: IConfigNode, path: string[]): undefined | IValueDescrip
     };
   }
 
-  return;
-}
-
-function findValueInObject(obj: any, path: string[]): undefined | IValueDescriptor {
-  const key = path.shift();
-  if (!key) {
-    return {
-      value: obj,
-      type: ValueType.Simple,
-    };
-  }
-
-  if (Object.keys(obj).includes(key)) {
-    return findValueInObject(obj[key], path);
-  }
-
-  return;
+  return undefined;
 }
 
 export {
diff --git a/src/core/configuration/utils.ts b/src/core/configuration/utils.ts
index bae98e1..b397381 100644
--- a/src/core/configuration/utils.ts
+++ b/src/core/configuration/utils.ts
@@ -1,4 +1,4 @@
-export function mergeNameParts(...args: string[]) {
+export function mergeNameParts(...args: string[]): string {
   return args.filter((value) => value).join('.');
 }
 
diff --git a/src/core/dx-template.ts b/src/core/dx-template.ts
index e09e2fa..65efd18 100644
--- a/src/core/dx-template.ts
+++ b/src/core/dx-template.ts
@@ -16,12 +16,15 @@ interface IDxTemplateData {
   onRendered?: () => void;
 }
 
+function unwrapElement(element: any): HTMLElement {
+  return element.get ? element.get(0) : element;
+}
+
 function createDxTemplate(
   createContentProvider: () => (props: ITemplateArgs) => any,
   templatesStore: TemplatesStore,
   keyFn?: (data: any) => string,
 ): IDxTemplate {
-
   const renderedTemplates = new DoubleKeyMap<any, HTMLElement | null, string>();
 
   return {
@@ -34,7 +37,7 @@ function createDxTemplate(
       if (prevTemplateId) {
         templateId = prevTemplateId;
       } else {
-        templateId = keyFn ? keyFn(data.model) : '__template_' + generateID();
+        templateId = keyFn ? keyFn(data.model) : `__template_${generateID()}`;
 
         if (data.model !== undefined) {
           renderedTemplates.set(key, templateId);
@@ -68,10 +71,6 @@ function createDxTemplate(
   };
 }
 
-function unwrapElement(element: any): HTMLElement {
-  return element.get ? element.get(0) : element;
-}
-
 export {
   IDxTemplate,
   createDxTemplate,
diff --git a/src/core/extension-component.ts b/src/core/extension-component.ts
index cea8145..be22896 100644
--- a/src/core/extension-component.ts
+++ b/src/core/extension-component.ts
@@ -1,8 +1,8 @@
 import { ComponentBase } from './component-base';
 
 class ExtensionComponent<P> extends ComponentBase<P> {
-  public componentDidMount() {
-    const onMounted = (this.props as any).onMounted;
+  public componentDidMount(): void {
+    const { onMounted } = this.props as any;
     if (onMounted) {
       onMounted(this._createWidget);
     } else {
diff --git a/src/core/helpers.ts b/src/core/helpers.ts
index 58ab657..51c07a7 100644
--- a/src/core/helpers.ts
+++ b/src/core/helpers.ts
@@ -5,7 +5,7 @@ export function generateID(): string {
 export class DoubleKeyMap<TKey1, TKey2, TValue> {
   private readonly _map: Map<TKey1, Map<TKey2, TValue>> = new Map();
 
-  public set({ key1, key2 }: { key1: TKey1, key2: TKey2 }, value: TValue) {
+  public set({ key1, key2 }: { key1: TKey1, key2: TKey2 }, value: TValue): void {
     let innerMap = this._map.get(key1);
     if (!innerMap) {
       innerMap = new Map<TKey2, TValue>();
diff --git a/src/core/nested-option.ts b/src/core/nested-option.ts
index 9d9df36..a82449c 100644
--- a/src/core/nested-option.ts
+++ b/src/core/nested-option.ts
@@ -9,9 +9,8 @@ interface INestedOptionMeta {
 }
 
 class NestedOption<P> extends React.PureComponent<P, any> {
-
-  public render() {
-    const {children: stateChildren} = this.props;
+  public render(): React.ReactNode {
+    const { children: stateChildren } = this.props;
     const children = React.Children.map(
       stateChildren,
       (child) => {
diff --git a/src/core/options-manager.ts b/src/core/options-manager.ts
index 1825598..79321a8 100644
--- a/src/core/options-manager.ts
+++ b/src/core/options-manager.ts
@@ -9,6 +9,8 @@ import { capitalizeFirstLetter } from './helpers';
 class OptionsManager {
   private readonly _guards: Record<string, number> = {};
 
+  private readonly _updatedOptions: Set<string> = new Set();
+
   private _templatesManager: TemplatesManager;
 
   private _instance: any;
@@ -24,23 +26,22 @@ class OptionsManager {
     this._wrapOptionValue = this._wrapOptionValue.bind(this);
   }
 
-  public setInstance(instance: any, config: IConfigNode) {
+  public setInstance(instance: unknown, config: IConfigNode): void {
     this._instance = instance;
     this._currentConfig = config;
   }
 
-  public getInitialOptions(rootNode: IConfigNode) {
+  public getInitialOptions(rootNode: IConfigNode): Record<string, any> {
     const config = buildConfig(rootNode, false);
 
-    for (const key of Object.keys(config.templates)) {
+    Object.keys(config.templates).forEach((key) => {
       this._templatesManager.add(key, config.templates[key]);
-    }
-
+    });
     const options: Record<string, any> = {};
 
-    for (const key of Object.keys(config.options)) {
+    Object.keys(config.options).forEach((key) => {
       options[key] = this._wrapOptionValue(key, config.options[key]);
-    }
+    });
 
     if (this._templatesManager.templatesCount > 0) {
       options.integrationOptions = {
@@ -51,7 +52,7 @@ class OptionsManager {
     return options;
   }
 
-  public update(config: IConfigNode) {
+  public update(config: IConfigNode): void {
     const changes = getChanges(config, this._currentConfig);
 
     if (!changes.options && !changes.templates && !changes.removedOptions.length) {
@@ -65,10 +66,9 @@ class OptionsManager {
       this._resetOption(optionName);
     });
 
-    for (const key of Object.keys(changes.templates)) {
+    Object.keys(changes.templates).forEach((key) => {
       this._templatesManager.add(key, changes.templates[key]);
-    }
-
+    });
     if (this._templatesManager.templatesCount > 0) {
       this._setValue(
         'integrationOptions',
@@ -78,9 +78,10 @@ class OptionsManager {
       );
     }
 
-    for (const key of Object.keys(changes.options)) {
+    Object.keys(changes.options).forEach((key) => {
+      this._updatedOptions.add(key);
       this._setValue(key, changes.options[key]);
-    }
+    });
 
     this._isUpdating = false;
     this._instance.endUpdate();
@@ -88,12 +89,16 @@ class OptionsManager {
     this._currentConfig = config;
   }
 
-  public onOptionChanged(e: { name: string, fullName: string, value: any }) {
+  public onOptionChanged(e: { name: string, fullName: string, value: any }): void {
     if (this._isUpdating) {
       return;
     }
 
-    this._callOptionChangeHandler(e.fullName, e.value);
+    if (this._updatedOptions.has(e.fullName)) {
+      this._updatedOptions.delete(e.fullName);
+    } else {
+      this._callOptionChangeHandler(e.fullName, e.value);
+    }
     const valueDescriptor = findValue(this._currentConfig, e.fullName.split('.'));
     if (!valueDescriptor) {
       return;
@@ -101,12 +106,12 @@ class OptionsManager {
 
     const { value, type } = valueDescriptor;
     if (type === ValueType.Complex) {
-      for (const key of Object.keys(value)) {
+      Object.keys(value).forEach((key) => {
         if (value[key] === e.value[key]) {
-          continue;
+          return;
         }
         this._setGuard(mergeNameParts(e.fullName, key), value[key]);
-      }
+      });
     } else {
       if (value === e.value) {
         return;
@@ -115,11 +120,11 @@ class OptionsManager {
     }
   }
 
-  public dispose() {
-    for (const optionName of Object.keys(this._guards)) {
+  public dispose(): void {
+    Object.keys(this._guards).forEach((optionName) => {
       window.clearTimeout(this._guards[optionName]);
       delete this._guards[optionName];
-    }
+    });
   }
 
   private _callOptionChangeHandler(optionName: string, optionValue: any) {
diff --git a/src/core/template-wrapper.ts b/src/core/template-wrapper.ts
index a99b3f5..7036a71 100644
--- a/src/core/template-wrapper.ts
+++ b/src/core/template-wrapper.ts
@@ -31,22 +31,22 @@ class TemplateWrapper extends React.PureComponent<ITemplateWrapperProps, ITempla
     this._onDxRemove = this._onDxRemove.bind(this);
   }
 
-  public componentDidMount() {
-    const {onRendered} = this.props;
+  public componentDidMount(): void {
+    const { onRendered } = this.props;
     onRendered?.();
 
     this._subscribeOnRemove();
   }
 
-  public componentDidUpdate() {
+  public componentDidUpdate(): void {
     this._subscribeOnRemove();
   }
 
-  public componentWillUnmount() {
+  public componentWillUnmount(): void {
     // Let React remove it itself
     // eslint-disable-next-line react/no-find-dom-node
     const node = ReactDOM.findDOMNode(this);
-    const {container} = this.props;
+    const { container } = this.props;
 
     if (node) {
       container.appendChild(node);
@@ -63,7 +63,7 @@ class TemplateWrapper extends React.PureComponent<ITemplateWrapperProps, ITempla
   private _subscribeOnRemove() {
     // eslint-disable-next-line react/no-find-dom-node
     const node = ReactDOM.findDOMNode(this);
-    const {removalListenerRequired} = this.state;
+    const { removalListenerRequired } = this.state;
 
     if (node && node.nodeType === Node.ELEMENT_NODE) {
       this._subscribeOnElementRemoval(node as Element);
@@ -86,13 +86,13 @@ class TemplateWrapper extends React.PureComponent<ITemplateWrapperProps, ITempla
   }
 
   private _onDxRemove(): void {
-    const {onRemoved} = this.props;
+    const { onRemoved } = this.props;
     onRemoved();
   }
 
-  public render() {
-    const {removalListenerRequired} = this.state;
-    const {content, container} = this.props;
+  public render(): React.ReactNode {
+    const { removalListenerRequired } = this.state;
+    const { content, container } = this.props;
     const removalListener = removalListenerRequired
       ? React.createElement('span', { style: removalListenerStyle, ref: this._removalListenerRef })
       : undefined;
diff --git a/src/core/template.ts b/src/core/template.ts
index a99cd5a..f3f77a6 100644
--- a/src/core/template.ts
+++ b/src/core/template.ts
@@ -22,7 +22,7 @@ interface ITemplateArgs {
 }
 
 class Template extends React.PureComponent<ITemplateProps, any> {
-  public render() {
+  public render(): React.ReactNode {
     return null;
   }
 }
@@ -43,7 +43,7 @@ const requiredPropsCheck = (props: Record<string, any>) => {
 
 function findProps(child: React.ReactElement<any>): ITemplateProps | undefined {
   if (child.type !== Template) {
-    return;
+    return undefined;
   }
   return {
     name: child.props.name,
diff --git a/src/core/templates-manager.ts b/src/core/templates-manager.ts
index ac4e65a..2fdd100 100644
--- a/src/core/templates-manager.ts
+++ b/src/core/templates-manager.ts
@@ -9,7 +9,7 @@ import { TemplatesStore } from './templates-store';
 function normalizeProps(props: ITemplateArgs): ITemplateArgs | ITemplateArgs['data'] {
   if (getConfigOption('useLegacyTemplateEngine')) {
     const model = props.data;
-    if (model && model.hasOwnProperty('key')) {
+    if (model && Object.prototype.hasOwnProperty.call(model, 'key')) {
       model.dxkey = model.key;
     }
     return model;
@@ -42,9 +42,10 @@ class TemplatesManager {
     this._templatesStore = templatesStore;
   }
 
-  public add(name: string, template: ITemplate) {
+  public add(name: string, template: ITemplate): void {
     this._templatesContent[name] = template.content;
-    const contentCreator = contentCreators[template.type].bind(this, () => this._templatesContent[name]);
+    const contentCreator = contentCreators[template.type]
+      .bind(this, () => this._templatesContent[name]);
     this._templates[name] = createDxTemplate(
       contentCreator,
       this._templatesStore,
diff --git a/src/core/templates-renderer.tsx b/src/core/templates-renderer.tsx
index dbe4737..c9acce6 100644
--- a/src/core/templates-renderer.tsx
+++ b/src/core/templates-renderer.tsx
@@ -1,3 +1,4 @@
+import { requestAnimationFrame } from 'devextreme/animation/frame';
 import { deferUpdate } from 'devextreme/core/utils/common';
 import * as React from 'react';
 
@@ -6,21 +7,22 @@ import { TemplatesStore } from './templates-store';
 class TemplatesRenderer extends React.PureComponent<{ templatesStore: TemplatesStore }> {
   private _updateScheduled = false;
 
-  public scheduleUpdate() {
+  public scheduleUpdate(useDeferUpdate: boolean): void {
     if (this._updateScheduled) {
       return;
     }
 
     this._updateScheduled = true;
 
-    deferUpdate(() => {
+    const updateFunc = useDeferUpdate ? deferUpdate : requestAnimationFrame;
+    updateFunc(() => {
       this.forceUpdate();
       this._updateScheduled = false;
     });
   }
 
-  public render() {
-    const {templatesStore} = this.props;
+  public render(): React.ReactNode {
+    const { templatesStore } = this.props;
     return React.createElement(
       React.Fragment,
       {},
diff --git a/src/core/widget-config.ts b/src/core/widget-config.ts
index 6216ae2..c990a96 100644
--- a/src/core/widget-config.ts
+++ b/src/core/widget-config.ts
@@ -3,6 +3,10 @@ import { ITemplateMeta } from './template';
 const elementPropNames = ['style', 'id'];
 const classNamePropName = 'className';
 
+function isIgnoredProp(name: string) {
+  return name === 'children' || name === classNamePropName || elementPropNames.indexOf(name) > -1;
+}
+
 function separateProps(
   props: Record<string, any>,
   defaultsProps: Record<string, string>,
@@ -49,10 +53,6 @@ function getClassName(props: Record<string, any>): string | undefined {
   return props[classNamePropName];
 }
 
-function isIgnoredProp(name: string) {
-  return name === 'children' || name === classNamePropName || elementPropNames.indexOf(name) > -1;
-}
-
 export {
   elementPropNames,
   getClassName,
diff --git a/tools/src/component-generator.ts b/tools/src/component-generator.ts
index 091008f..dbb1885 100644
--- a/tools/src/component-generator.ts
+++ b/tools/src/component-generator.ts
@@ -75,16 +75,307 @@ const TYPE_KEY_FN = '(data: any) => string';
 const TYPE_RENDER = '(...params: any) => React.ReactNode';
 const TYPE_COMPONENT = 'React.ComponentType<any>';
 
+function getIndent(indent: number) {
+  return Array(indent * 2 + 1).join(' ');
+}
+
+const renderReExport: (model: { path: string, fileName: string }) => string = createTempate(
+  '/** @deprecated Use \'devextreme-react/<#= it.fileName #>\' file instead */\n'
++ 'export * from "<#= it.path #>";\n'
++ 'export { default } from "<#= it.path #>";\n',
+);
+
 function generateReExport(path: string, fileName: string): string {
   return renderReExport({ path, fileName });
 }
 
-const renderReExport: (model: { path: string, fileName: string }) => string = createTempate(
-  '/** @deprecated Use \'devextreme-react/<#= it.fileName #>\' file instead */\n' +
-'export * from "<#= it.path #>";\n' +
-'export { default } from "<#= it.path #>";\n',
+const renderObjectEntry: (model: {
+  key: string;
+  value: string;
+}) => string = createTempate(`
+    <#= it.key #>: "<#= it.value #>"
+`.trimRight());
+
+function renderObject(props: IOption[], indent: number): string {
+  let result = '{';
+
+  indent += 1;
+
+  props.forEach((opt) => {
+    result += `\n${getIndent(indent)}${opt.name}?: `;
+    if (opt.nested && isNotEmptyArray(opt.nested)) {
+      result += renderObject(opt.nested, indent);
+      if (opt.isArray) { result += '[]'; }
+    } else {
+      result += opt.type;
+    }
+    result += ';';
+  });
+
+  indent -= 1;
+  result += `\n${getIndent(indent)}}`;
+  return result;
+}
+
+const renderTemplateOption: (model: {
+  actualOptionName: string;
+  render: string;
+  component: string;
+}) => string = createTempate(`
+  {
+    tmplOption: "<#= it.actualOptionName #>",
+    render: "<#= it.render #>",
+    component: "<#= it.component #>",
+    keyFn: "<#= it.keyFn #>"
+  }
+`.trim());
+
+const renderPropTyping: (model: IRenderedPropTyping) => string = createTempate(
+  '  <#= it.propName #>: '
+
++ '<#? it.renderedTypes.length === 1 #>'
+    + '<#= it.renderedTypes[0] #>'
+
++ '<#??#>'
+
+    + 'PropTypes.oneOfType([\n'
+    + '    <#= it.renderedTypes.join(\',\\n    \') #>\n'
+    + '  ])'
++ '<#?#>',
+);
+
+const renderModule: (model: {
+  renderedImports: string;
+  renderedOptionsInterface?: string;
+  renderedComponent: string;
+  renderedNestedComponents?: string[];
+  defaultExport: string;
+  renderedExports: string;
+}) => string = createTempate(
+  '<#= it.renderedImports #>\n'
+
++ '<#? it.renderedOptionsInterface #>'
+    + '<#= it.renderedOptionsInterface #>\n\n'
++ '<#?#>'
+
++ '<#= it.renderedComponent #>'
+
++ '<#? it.renderedNestedComponents #>'
+    + '<#~ it.renderedNestedComponents :nestedComponent #>\n\n'
+        + '<#= nestedComponent #>'
+    + '<#~#>\n\n'
++ '<#?#>'
+
++ 'export default <#= it.defaultExport #>;\n'
++ `export {
+<#= it.renderedExports #>
+};
+`,
+);
+
+const renderImports: (model: {
+  dxExportPath: string;
+  baseComponentPath: string;
+  baseComponentName: string;
+  widgetName: string;
+  optionsAliasName?: string;
+  hasExtraOptions: boolean;
+  hasPropTypings: boolean;
+  configComponentPath?: string;
+}) => string = createTempate(
+  'import <#= it.widgetName #>, {\n'
++ '    IOptions<#? it.optionsAliasName #> as <#= it.optionsAliasName #><#?#>\n'
++ '} from "<#= it.dxExportPath #>";\n\n'
+
++ '<#? it.hasPropTypings #>'
+    + 'import * as PropTypes from "prop-types";\n'
++ '<#?#>'
+
++ 'import { <#= it.baseComponentName #> as BaseComponent'
+    + '<#? it.hasExtraOptions #>'
+        + ', IHtmlOptions'
+    + '<#?#>'
++ ' } from "<#= it.baseComponentPath #>";\n'
+
++ '<#? it.configComponentPath #>'
+    + 'import NestedOption from "<#= it.configComponentPath #>";\n'
++ '<#?#>',
+);
+
+const renderNestedComponent: (model: {
+  className: string;
+  propsType: string;
+  optionName: string;
+  isCollectionItem?: boolean;
+  predefinedProps?: Array<{
+    name: string;
+    value: any;
+  }>;
+  expectedChildren?: IExpectedChild[];
+  renderedType: string;
+  renderedSubscribableOptions?: string[];
+  renderedTemplateProps?: string[];
+  owners: string[];
+}) => string = createTempate(
+  `${'// owners:\n'
++ '<#~ it.owners : owner #>'
+    + '// <#= owner #>\n'
++ '<#~#>'
+
++ 'interface <#= it.propsType #> <#= it.renderedType #>\n'
+
++ 'class <#= it.className #> extends NestedOption<<#= it.propsType #>> {'}${
+    L1}public static OptionName = "<#= it.optionName #>";`
+
++ `<#? it.isCollectionItem #>${
+  L1}public static IsCollectionItem = true;`
++ '<#?#>'
+
++ `<#? it.renderedSubscribableOptions #>${
+  L1}public static DefaultsProps = {<#= it.renderedSubscribableOptions.join(',') #>${
+  L1}};`
++ '<#?#>'
+
++ `<#? it.expectedChildren #>${
+  L1}public static ExpectedChildren = {`
+
+    + `<#~ it.expectedChildren : child #>${
+      L2}<#= child.componentName #>:`
+        + ' { optionName: "<#= child.optionName #>", isCollectionItem: <#= !!child.isCollectionItem #> },'
+    + `<#~#>\b${
+
+      L1}};`
++ '<#?#>'
+
++ `<#? it.renderedTemplateProps #>${
+  L1}public static TemplateProps = [<#= it.renderedTemplateProps.join(', ') #>];`
++ '<#?#>'
+
++ `<#? it.predefinedProps #>${
+  L1}public static PredefinedProps = {`
+        + `<#~ it.predefinedProps : prop #>${
+          L2}<#= prop.name #>: "<#= prop.value #>",`
+        + `<#~#>\b${
+          L1}};`
++ '<#?#>'
+
++ '\n}',
+);
+
+const renderOptionsInterface: (model: {
+  optionsName: string;
+  templates: Array<{
+    render: string;
+    component: string;
+  }>;
+  defaultProps: Array<{
+    name: string;
+    type: string;
+  }>;
+  onChangeEvents: Array<{
+    name: string;
+    type: string;
+  }>;
+}) => string = createTempate(
+  'interface <#= it.optionsName #> extends IOptions, IHtmlOptions {\n'
+
++ '<#~ it.templates :template #>'
+    + `  <#= template.render #>?: ${TYPE_RENDER};\n`
+    + `  <#= template.component #>?: ${TYPE_COMPONENT};\n`
+    + `  <#= template.keyFn #>?: ${TYPE_KEY_FN};\n`
++ '<#~#>'
+
++ '<#~ it.defaultProps :prop #>'
+    + '  <#= prop.name #>?: <#= prop.type #>;\n'
++ '<#~#>'
+
++ '<#~ it.onChangeEvents :prop #>'
+    + '  <#= prop.name #>?: <#= prop.type #>;\n'
++ '<#~#>'
+
++ '}',
+);
+
+const renderComponent: (model: {
+  className: string;
+  widgetName: string;
+  optionsName: string;
+  expectedChildren?: IExpectedChild[];
+  renderedDefaultProps?: string[];
+  renderedTemplateProps?: string[];
+  renderedPropTypings?: string[];
+}) => string = createTempate(
+  `${`class <#= it.className #> extends BaseComponent<<#= it.optionsName #>> {
+
+  public get instance(): <#= it.widgetName #> {
+    return this._instance;
+  }
+
+  protected _WidgetClass = <#= it.widgetName #>;\n`
+
++ '<#? it.renderedDefaultProps #>'}${
+    L1}protected _defaults = {<#= it.renderedDefaultProps.join(',') #>${
+    L1}};\n`
++ '<#?#>'
+
++ `<#? it.expectedChildren #>${
+  L1}protected _expectedChildren = {`
+
++ `<#~ it.expectedChildren : child #>${
+  L2}<#= child.componentName #>:`
+    + ' { optionName: "<#= child.optionName #>", isCollectionItem: <#= !!child.isCollectionItem #> },'
++ `<#~#>\b${
+
+  L1}};\n`
++ '<#?#>'
+
++ `<#? it.renderedTemplateProps #>
+  protected _templateProps = [<#= it.renderedTemplateProps.join(', ') #>];
+<#?#>}\n`
+
++ '<#? it.renderedPropTypings #>'
+    + '(<#= it.className #> as any).propTypes = {\n'
+        + '<#= it.renderedPropTypings.join(\',\\n\') #>\n'
+    + '};\n'
++ '<#?#>',
 );
 
+function renderExports(exportsNames: string[]) {
+  return exportsNames
+    .map((exportName) => getIndent(1) + exportName)
+    .join(',\n');
+}
+
+function formatTemplatePropName(name: string, suffix: string): string {
+  return lowercaseFirst(name.replace(/template$/i, suffix));
+}
+
+function createTemplateDto(templates: string[] | undefined) {
+  return templates
+    ? templates.map((actualOptionName) => ({
+      actualOptionName,
+      render: formatTemplatePropName(actualOptionName, 'Render'),
+      component: formatTemplatePropName(actualOptionName, 'Component'),
+      keyFn: formatTemplatePropName(actualOptionName, 'KeyFn'),
+    }))
+    : undefined;
+}
+
+function buildPropsTypeName(className: string) {
+  return `I${className}Props`;
+}
+
+function createPropTypingModel(typing: IPropTyping): IRenderedPropTyping {
+  const types = typing.types.map((t) => `PropTypes.${t}`);
+  if (typing.acceptableValues && isNotEmptyArray(typing.acceptableValues)) {
+    types.push(`PropTypes.oneOf([\n    ${typing.acceptableValues.join(',\n    ')}\n  ])`);
+  }
+  return {
+    propName: typing.propName,
+    renderedTypes: types,
+  };
+}
+
 function generate(component: IComponent): string {
   const nestedComponents = component.nestedComponents
     ? component.nestedComponents
@@ -94,7 +385,6 @@ function generate(component: IComponent): string {
         const nestedSubscribableOptions = options.filter((o) => o.isSubscribable);
         let renderedSubscribableOptions: string[] | undefined;
         if (isNotEmptyArray(nestedSubscribableOptions)) {
-
           nestedSubscribableOptions.forEach((o) => {
             options.push({
               ...o,
@@ -105,12 +395,10 @@ function generate(component: IComponent): string {
             });
           });
 
-          renderedSubscribableOptions = nestedSubscribableOptions.map((o) =>
-            renderObjectEntry({
-              key: `default${uppercaseFirst(o.name)}`,
-              value: o.name,
-            }),
-          );
+          renderedSubscribableOptions = nestedSubscribableOptions.map((o) => renderObjectEntry({
+            key: `default${uppercaseFirst(o.name)}`,
+            value: o.name,
+          }));
         }
         const nestedTemplates = createTemplateDto(c.templates);
         if (nestedTemplates) {
@@ -131,10 +419,11 @@ function generate(component: IComponent): string {
         let predefinedProps: Array<{ name: string; value: string }> | undefined;
         if (c.predefinedProps) {
           predefinedProps = [];
-          for (const name of Object.keys(c.predefinedProps)) {
-            predefinedProps.push({
-              name,
-              value: c.predefinedProps[name],
+          if (c.predefinedProps) {
+            predefinedProps = [];
+            Object.entries(c.predefinedProps).forEach((prop) => {
+              const [name, value] = prop;
+              predefinedProps?.push({ name, value });
             });
           }
         }
@@ -185,7 +474,7 @@ function generate(component: IComponent): string {
     : undefined;
 
   const hasExtraOptions = !component.isExtension;
-  const widgetName =  `dx${uppercaseFirst(component.name)}`;
+  const widgetName = `dx${uppercaseFirst(component.name)}`;
 
   const renderedPropTypings = component.propTypings
     ? component.propTypings
@@ -197,13 +486,17 @@ function generate(component: IComponent): string {
 
     renderedImports: renderImports({
       dxExportPath: component.dxExportPath,
-      baseComponentPath: component.isExtension ? component.extensionComponentPath : component.baseComponentPath,
+      baseComponentPath: component.isExtension
+        ? component.extensionComponentPath
+        : component.baseComponentPath,
       baseComponentName: component.isExtension ? 'ExtensionComponent' : 'Component',
       widgetName,
       optionsAliasName: hasExtraOptions ? undefined : optionsName,
       hasExtraOptions,
       hasPropTypings: isNotEmptyArray(renderedPropTypings),
-      configComponentPath: isNotEmptyArray(nestedComponents) ? component.configComponentPath : undefined,
+      configComponentPath: isNotEmptyArray(nestedComponents)
+        ? component.configComponentPath
+        : undefined,
     }),
 
     renderedOptionsInterface: !hasExtraOptions ? undefined : renderOptionsInterface({
@@ -218,12 +511,10 @@ function generate(component: IComponent): string {
       widgetName,
       optionsName,
       renderedTemplateProps: templates && templates.map(renderTemplateOption),
-      renderedDefaultProps: defaultProps && defaultProps.map((o) =>
-        renderObjectEntry({
-          key: o.name,
-          value: o.actualOptionName,
-        }),
-      ),
+      renderedDefaultProps: defaultProps && defaultProps.map((o) => renderObjectEntry({
+        key: o.name,
+        value: o.actualOptionName,
+      })),
       renderedPropTypings,
       expectedChildren: component.expectedChildren,
     }),
@@ -235,296 +526,6 @@ function generate(component: IComponent): string {
   });
 }
 
-function buildPropsTypeName(className: string) {
-  return `I${className}Props`;
-}
-
-function createTemplateDto(templates: string[] | undefined) {
-  return templates
-    ? templates.map((actualOptionName) => ({
-      actualOptionName,
-      render: formatTemplatePropName(actualOptionName, 'Render'),
-      component: formatTemplatePropName(actualOptionName, 'Component'),
-      keyFn: formatTemplatePropName(actualOptionName, 'KeyFn'),
-    }))
-    : undefined;
-}
-
-function formatTemplatePropName(name: string, suffix: string): string {
-  return lowercaseFirst(name.replace(/template$/i, suffix));
-}
-
-function createPropTypingModel(typing: IPropTyping): IRenderedPropTyping {
-  const types = typing.types.map((t) => 'PropTypes.' + t);
-  if (typing.acceptableValues && isNotEmptyArray(typing.acceptableValues)) {
-    types.push(`PropTypes.oneOf([\n    ${typing.acceptableValues.join(',\n    ')}\n  ])`);
-  }
-  return {
-    propName: typing.propName,
-    renderedTypes: types,
-  };
-}
-
-const renderModule: (model: {
-  renderedImports: string;
-  renderedOptionsInterface?: string;
-  renderedComponent: string;
-  renderedNestedComponents?: string[];
-  defaultExport: string;
-  renderedExports: string;
-}) => string = createTempate(
-  '<#= it.renderedImports #>' + '\n' +
-
-'<#? it.renderedOptionsInterface #>' +
-    '<#= it.renderedOptionsInterface #>' + '\n' + '\n' +
-'<#?#>' +
-
-'<#= it.renderedComponent #>' +
-
-'<#? it.renderedNestedComponents #>' +
-    '<#~ it.renderedNestedComponents :nestedComponent #>' + '\n\n' +
-        '<#= nestedComponent #>' +
-    '<#~#>' + '\n\n' +
-'<#?#>' +
-
-'export default <#= it.defaultExport #>;' + '\n' +
-`export {
-<#= it.renderedExports #>
-};
-`);
-
-const renderImports: (model: {
-  dxExportPath: string;
-  baseComponentPath: string;
-  baseComponentName: string;
-  widgetName: string;
-  optionsAliasName?: string;
-  hasExtraOptions: boolean;
-  hasPropTypings: boolean;
-  configComponentPath?: string;
-}) => string = createTempate(
-  `import <#= it.widgetName #>, {
-    IOptions` + '<#? it.optionsAliasName #> as <#= it.optionsAliasName #><#?#>' + '\n' +
-'} from "<#= it.dxExportPath #>";' + '\n' + '\n' +
-
-'<#? it.hasPropTypings #>' +
-    'import * as PropTypes from "prop-types";' + '\n' +
-'<#?#>' +
-
-'import { <#= it.baseComponentName #> as BaseComponent' +
-    '<#? it.hasExtraOptions #>' +
-        ', IHtmlOptions' +
-    '<#?#>' +
-' } from "<#= it.baseComponentPath #>";' + '\n' +
-
-'<#? it.configComponentPath #>' +
-    'import NestedOption from "<#= it.configComponentPath #>";' + '\n' +
-'<#?#>',
-);
-
-const renderOptionsInterface: (model: {
-  optionsName: string;
-  templates: Array<{
-    render: string;
-    component: string;
-  }>;
-  defaultProps: Array<{
-    name: string;
-    type: string;
-  }>;
-  onChangeEvents: Array<{
-    name: string;
-    type: string;
-  }>;
-}) => string = createTempate(
-  'interface <#= it.optionsName #> extends IOptions, IHtmlOptions {' + '\n' +
-
-'<#~ it.templates :template #>' +
-    `  <#= template.render #>?: ${TYPE_RENDER};` + '\n' +
-    `  <#= template.component #>?: ${TYPE_COMPONENT};` + '\n' +
-    `  <#= template.keyFn #>?: ${TYPE_KEY_FN};` + '\n' +
-'<#~#>' +
-
-'<#~ it.defaultProps :prop #>' +
-    '  <#= prop.name #>?: <#= prop.type #>;' + '\n' +
-'<#~#>' +
-
-'<#~ it.onChangeEvents :prop #>' +
-    '  <#= prop.name #>?: <#= prop.type #>;' + '\n' +
-'<#~#>' +
-
-'}',
-);
-
-const renderComponent: (model: {
-  className: string;
-  widgetName: string;
-  optionsName: string;
-  expectedChildren?: IExpectedChild[];
-  renderedDefaultProps?: string[];
-  renderedTemplateProps?: string[];
-  renderedPropTypings?: string[];
-}) => string = createTempate(
-  `class <#= it.className #> extends BaseComponent<<#= it.optionsName #>> {
-
-  public get instance(): <#= it.widgetName #> {
-    return this._instance;
-  }
-
-  protected _WidgetClass = <#= it.widgetName #>;\n` +
-
-'<#? it.renderedDefaultProps #>' +
-L1 + 'protected _defaults = {<#= it.renderedDefaultProps.join(\',\') #>' +
-L1 +  '};\n' +
-'<#?#>' +
-
-'<#? it.expectedChildren #>' +
-L1 + 'protected _expectedChildren = {' +
-
-'<#~ it.expectedChildren : child #>' +
-L2 + '<#= child.componentName #>:' +
-    ' { optionName: "<#= child.optionName #>", isCollectionItem: <#= !!child.isCollectionItem #> },' +
-'<#~#>' + '\b' +
-
-L1 +  '};\n' +
-'<#?#>' +
-
-`<#? it.renderedTemplateProps #>
-  protected _templateProps = [<#= it.renderedTemplateProps.join(', ') #>];
-<#?#>}` + '\n' +
-
-'<#? it.renderedPropTypings #>' +
-    '(<#= it.className #> as any).propTypes = {' + '\n' +
-        '<#= it.renderedPropTypings.join(\',\\n\') #>' + '\n' +
-    '};' + '\n' +
-'<#?#>',
-);
-
-const renderNestedComponent: (model: {
-  className: string;
-  propsType: string;
-  optionName: string;
-  isCollectionItem?: boolean;
-  predefinedProps?: Array<{
-    name: string;
-    value: any;
-  }>;
-  expectedChildren?: IExpectedChild[];
-  renderedType: string;
-  renderedSubscribableOptions?: string[];
-  renderedTemplateProps?: string[];
-  owners: string[];
-}) => string = createTempate(
-  '// owners:\n' +
-'<#~ it.owners : owner #>' +
-    '// <#= owner #>\n' +
-'<#~#>' +
-
-'interface <#= it.propsType #> <#= it.renderedType #>\n' +
-
-'class <#= it.className #> extends NestedOption<<#= it.propsType #>> {' +
-L1 + 'public static OptionName = "<#= it.optionName #>";' +
-
-'<#? it.isCollectionItem #>' +
-    L1 + 'public static IsCollectionItem = true;' +
-'<#?#>' +
-
-'<#? it.renderedSubscribableOptions #>' +
-    L1 + 'public static DefaultsProps = {<#= it.renderedSubscribableOptions.join(\',\') #>' +
-    L1 + '};' +
-'<#?#>' +
-
-'<#? it.expectedChildren #>' +
-    L1 + 'public static ExpectedChildren = {' +
-
-    '<#~ it.expectedChildren : child #>' +
-    L2 + '<#= child.componentName #>:' +
-        ' { optionName: "<#= child.optionName #>", isCollectionItem: <#= !!child.isCollectionItem #> },' +
-    '<#~#>' + '\b' +
-
-    L1 +  '};' +
-'<#?#>' +
-
-'<#? it.renderedTemplateProps #>' +
-    L1 + 'public static TemplateProps = [<#= it.renderedTemplateProps.join(\', \') #>];' +
-'<#?#>' +
-
-'<#? it.predefinedProps #>' +
-    L1 + 'public static PredefinedProps = {' +
-        '<#~ it.predefinedProps : prop #>' +
-            L2 + '<#= prop.name #>: "<#= prop.value #>",' +
-        '<#~#>' + '\b' +
-    L1 + '};' +
-'<#?#>' +
-
-'\n}',
-);
-
-const renderTemplateOption: (model: {
-  actualOptionName: string;
-  render: string;
-  component: string;
-}) => string = createTempate(`
-  {
-    tmplOption: "<#= it.actualOptionName #>",
-    render: "<#= it.render #>",
-    component: "<#= it.component #>",
-    keyFn: "<#= it.keyFn #>"
-  }
-`.trim());
-
-const renderPropTyping: (model: IRenderedPropTyping) => string = createTempate(
-  '  <#= it.propName #>: ' +
-
-'<#? it.renderedTypes.length === 1 #>' +
-    '<#= it.renderedTypes[0] #>' +
-
-'<#??#>' +
-
-    'PropTypes.oneOfType([' + '\n' +
-    '    <#= it.renderedTypes.join(\',\\n    \') #>' + '\n' +
-    '  ])' +
-'<#?#>',
-);
-
-const renderObjectEntry: (model: {
-  key: string;
-  value: string;
-}) => string = createTempate(`
-    <#= it.key #>: "<#= it.value #>"
-`.trimRight());
-
-function renderObject(props: IOption[], indent: number): string {
-  let result = '{';
-
-  indent += 1;
-
-  props.forEach((opt) => {
-    result += '\n' + getIndent(indent) + opt.name + '?: ';
-    if (opt.nested && isNotEmptyArray(opt.nested)) {
-      result += renderObject(opt.nested, indent);
-      if (opt.isArray) { result += '[]'; }
-    } else {
-      result += opt.type;
-    }
-    result += ';';
-  });
-
-  indent -= 1;
-  result +=  '\n' + getIndent(indent) + '}';
-  return result;
-}
-
-function getIndent(indent: number) {
-  return Array(indent * 2 + 1).join(' ');
-}
-
-function renderExports(exportsNames: string[]) {
-  return exportsNames
-    .map((exportName) => getIndent(1) + exportName)
-    .join(',\n');
-}
-
 export default generate;
 export {
   IComponent,
diff --git a/tools/src/converter.ts b/tools/src/converter.ts
index e461a1f..af16233 100644
--- a/tools/src/converter.ts
+++ b/tools/src/converter.ts
@@ -1,25 +1,14 @@
 import { ICustomType, ITypeDescr } from '../integration-data-model';
 import { lowercaseFirst } from './helpers';
 
-function convertTypes(
-  types: ITypeDescr[] | undefined | null,
-  customTypes?: Record<string, ICustomType>,
-): string[] | undefined {
-  if (types === undefined || types === null || types.length === 0) {
-    return undefined;
-  }
-
-  if (customTypes) {
-    types.push(...expandTypes(types, customTypes));
-  }
-
-  const convertedTypes = new Set(types.map(convertType));
-  if (convertedTypes.has('Any')) {
-    return undefined;
-  }
-
-  return Array.from(convertedTypes);
-}
+const inputTypes = {
+  array: 'Array',
+  string: 'String',
+  number: 'Number',
+  object: 'Object',
+  bool: 'Boolean',
+  func: 'Function',
+};
 
 function expandTypes(types: ITypeDescr[], customTypes: Record<string, ICustomType>): ITypeDescr[] {
   const expandedTypes: ITypeDescr[] = [];
@@ -35,7 +24,7 @@ function expandTypes(types: ITypeDescr[], customTypes: Record<string, ICustomTyp
 }
 
 function convertType(typeDescr: ITypeDescr): string {
-  const type = typeDescr.type;
+  const { type } = typeDescr;
   switch (type) {
     case inputTypes.string:
     case inputTypes.number:
@@ -59,14 +48,25 @@ function convertType(typeDescr: ITypeDescr): string {
   return 'Any';
 }
 
-const inputTypes = {
-  array: 'Array',
-  string: 'String',
-  number: 'Number',
-  object: 'Object',
-  bool: 'Boolean',
-  func: 'Function',
-};
+function convertTypes(
+  types: ITypeDescr[] | undefined | null,
+  customTypes?: Record<string, ICustomType>,
+): string[] | undefined {
+  if (types === undefined || types === null || types.length === 0) {
+    return undefined;
+  }
+
+  if (customTypes) {
+    types.push(...expandTypes(types, customTypes));
+  }
+
+  const convertedTypes = new Set(types.map(convertType));
+  if (convertedTypes.has('Any')) {
+    return undefined;
+  }
+
+  return Array.from(convertedTypes);
+}
 
 export {
   convertTypes,
diff --git a/tools/src/generator.ts b/tools/src/generator.ts
index a593692..b91f8d9 100644
--- a/tools/src/generator.ts
+++ b/tools/src/generator.ts
@@ -38,56 +38,92 @@ import {
   uppercaseFirst,
 } from './helpers';
 
-function generate({
-  metaData: rawData,
-  components: { baseComponent, extensionComponent, configComponent },
-  out,
-  widgetsPackage,
-}: {
-  metaData: IModel,
-  components: {
-    baseComponent: string,
-    extensionComponent: string,
-    configComponent: string,
-  },
-  out: {
-    componentsDir: string,
-    oldComponentsDir: string,
-    indexFileName: string
-  },
-  widgetsPackage: string
-}) {
-  const modulePaths: IReExport[] = [];
+function mapSubscribableOption(prop: IProp): ISubscribableOption {
+  return {
+    name: prop.name,
+    type: 'any',
+    isSubscribable: prop.isSubscribable || undefined,
+  };
+}
 
-  rawData.widgets.forEach((data) => {
-    const widgetFile = mapWidget(
-      data,
-      baseComponent,
-      extensionComponent,
-      configComponent,
-      rawData.customTypes,
-      widgetsPackage,
-    );
-    const widgetFilePath = joinPaths(out.componentsDir, widgetFile.fileName);
-    const indexFileDir = getDirName(out.indexFileName);
+function isNestedOptionArray(prop: IProp): boolean {
+  return isNotEmptyArray(prop.types) && (prop.types[0].type === 'Array');
+}
 
-    writeFile(widgetFilePath, generateComponent(widgetFile.component), { encoding: 'utf8' });
-    modulePaths.push({
-      name: widgetFile.component.name,
-      path: './' + removeExtension(getRelativePath(indexFileDir, widgetFilePath)).replace(pathSeparator, '/'),
-    });
+function mapOption(prop: IProp): IOption {
+  return isEmptyArray(prop.props)
+    ? {
+      name: prop.name,
+      type: 'any',
+      isSubscribable: prop.isSubscribable || undefined,
 
-    writeFile(
-      joinPaths(out.oldComponentsDir, widgetFile.fileName),
-      generateReExport(
-        normalizePath('./' + removeExtension(getRelativePath(out.oldComponentsDir, widgetFilePath)))
-          .replace(pathSeparator, '/'),
-        removeExtension(widgetFile.fileName),
-      ),
-    );
+    } : {
+      name: prop.name,
+      isSubscribable: prop.isSubscribable || undefined,
+      nested: prop.props.map(mapOption),
+      isArray: isNestedOptionArray(prop),
+    };
+}
+
+function extractNestedComponents(
+  props: IComplexProp[],
+  rawWidgetName: string,
+  widgetName: string,
+): INestedComponent[] {
+  const nameClassMap: Record<string, string> = {};
+  nameClassMap[rawWidgetName] = widgetName;
+  props.forEach((p) => {
+    nameClassMap[p.name] = uppercaseFirst(p.name);
   });
 
-  writeFile(out.indexFileName, generateIndex(modulePaths), { encoding: 'utf8' });
+  return props.map((p) => ({
+    className: nameClassMap[p.name],
+    owners: p.owners.map((o) => nameClassMap[o]),
+    optionName: p.optionName,
+    options: p.props.map(mapOption),
+    isCollectionItem: p.isCollectionItem,
+    templates: p.templates,
+    predefinedProps: p.predefinedProps,
+    expectedChildren: p.nesteds,
+  }));
+}
+
+function createPropTyping(
+  option: IProp,
+  customTypes: Record<string, ICustomType>,
+): IPropTyping | null {
+  const isRestrictedType = (t: ITypeDescr): boolean => t.acceptableValues?.length > 0;
+
+  const rawTypes = option.types.filter((t) => !isRestrictedType(t));
+  const restrictedTypes = option.types.filter((t) => isRestrictedType(t));
+
+  const types = convertTypes(rawTypes, customTypes);
+
+  if (restrictedTypes.length > 0) {
+    return {
+      propName: option.name,
+      types: types || [],
+      acceptableValues: restrictedTypes[0].acceptableValues,
+    };
+  }
+
+  if ((!types || types.length === 0)) {
+    return null;
+  }
+
+  return {
+    propName: option.name,
+    types,
+  };
+}
+
+function extractPropTypings(
+  options: IProp[],
+  customTypes: Record<string, ICustomType>,
+): (IPropTyping | null)[] {
+  return options
+    .map((o) => createPropTyping(o, customTypes))
+    .filter((t) => t != null);
 }
 
 function mapWidget(
@@ -134,82 +170,56 @@ function mapWidget(
   };
 }
 
-function extractNestedComponents(props: IComplexProp[], rawWidgetName: string, widgetName: string): INestedComponent[] {
-  const nameClassMap: Record<string, string> = {};
-  nameClassMap[rawWidgetName] = widgetName;
-  props.forEach((p) => {
-    nameClassMap[p.name] = uppercaseFirst(p.name);
-  });
-
-  return props.map((p) => {
-    return {
-      className: nameClassMap[p.name],
-      owners: p.owners.map((o) => nameClassMap[o]),
-      optionName: p.optionName,
-      options: p.props.map(mapOption),
-      isCollectionItem: p.isCollectionItem,
-      templates: p.templates,
-      predefinedProps: p.predefinedProps,
-      expectedChildren: p.nesteds,
-    };
-  });
-}
-
-function extractPropTypings(options: IProp[], customTypes: Record<string, ICustomType>): IPropTyping[] {
-  return options
-    .map((o) => createPropTyping(o, customTypes))
-    .filter((t) => t != null);
-}
-
-function createPropTyping(option: IProp, customTypes: Record<string, ICustomType>): IPropTyping {
-  const isRestrictedType = (t: ITypeDescr): boolean => t.acceptableValues && t.acceptableValues.length > 0;
-
-  const rawTypes = option.types.filter((t) => !isRestrictedType(t));
-  const restrictedTypes = option.types.filter((t) => isRestrictedType(t));
-
-  const types = convertTypes(rawTypes, customTypes);
-
-  if (restrictedTypes.length > 0) {
-    return {
-      propName: option.name,
-      types: types || [],
-      acceptableValues: restrictedTypes[0].acceptableValues,
-    };
-  }
+function generate({
+  metaData: rawData,
+  components: { baseComponent, extensionComponent, configComponent },
+  out,
+  widgetsPackage,
+}: {
+  metaData: IModel,
+  components: {
+    baseComponent: string,
+    extensionComponent: string,
+    configComponent: string,
+  },
+  out: {
+    componentsDir: string,
+    oldComponentsDir: string,
+    indexFileName: string
+  },
+  widgetsPackage: string
+}): void {
+  const modulePaths: IReExport[] = [];
 
-  if ((!types || types.length === 0)) {
-    return null;
-  }
+  rawData.widgets.forEach((data) => {
+    const widgetFile = mapWidget(
+      data,
+      baseComponent,
+      extensionComponent,
+      configComponent,
+      rawData.customTypes,
+      widgetsPackage,
+    );
+    const widgetFilePath = joinPaths(out.componentsDir, widgetFile.fileName);
+    const indexFileDir = getDirName(out.indexFileName);
 
-  return {
-    propName: option.name,
-    types,
-  };
-}
+    writeFile(widgetFilePath, generateComponent(widgetFile.component), { encoding: 'utf8' });
+    modulePaths.push({
+      name: widgetFile.component.name,
+      path: `./${removeExtension(getRelativePath(indexFileDir, widgetFilePath)).replace(pathSeparator, '/')}`,
+    });
 
-function mapOption(prop: IProp): IOption {
-  return isEmptyArray(prop.props) ?
-    {
-      name: prop.name,
-      type: 'any',
-      isSubscribable: prop.isSubscribable || undefined,
+    writeFile(
+      joinPaths(out.oldComponentsDir, widgetFile.fileName),
+      generateReExport(
+        normalizePath(`./${removeExtension(getRelativePath(out.oldComponentsDir, widgetFilePath))}`)
+          .replace(pathSeparator, '/'),
+        removeExtension(widgetFile.fileName),
+      ),
+    );
+  });
 
-    } : {
-      name: prop.name,
-      isSubscribable: prop.isSubscribable || undefined,
-      nested: prop.props.map(mapOption),
-      isArray: isNestedOptionArray(prop),
-    };
-}
-function isNestedOptionArray(prop: IProp): boolean {
-  return isNotEmptyArray(prop.types) && (prop.types[0].type === 'Array');
-}
-function mapSubscribableOption(prop: IProp): ISubscribableOption {
-  return {
-    name: prop.name,
-    type: 'any',
-    isSubscribable: prop.isSubscribable || undefined,
-  };
+  writeFile(out.indexFileName, generateIndex(modulePaths), { encoding: 'utf8' });
 }
 
 export default generate;
diff --git a/tools/src/helpers.ts b/tools/src/helpers.ts
index 0a5b203..7aa72b5 100644
--- a/tools/src/helpers.ts
+++ b/tools/src/helpers.ts
@@ -1,8 +1,8 @@
 import * as dasherize from 'dasherize';
 import { extname as getPathExtension } from 'path';
 
-export function removeExtension(path: string) {
-  return path.slice(0, - getPathExtension(path).length);
+export function removeExtension(path: string): string {
+  return path.slice(0, -getPathExtension(path).length);
 }
 
 export function removePrefix(value: string, prefix: string): string {
@@ -26,24 +26,24 @@ export function compareStrings(a: string, b: string): number {
 }
 
 export function createKeyComparator<T>(keyGetter: (x: T) => string) {
-  return (a: T, b: T) => compareStrings(keyGetter(a), keyGetter(b));
+  return (a: T, b: T): number => compareStrings(keyGetter(a), keyGetter(b));
 }
 
-export function removeElement<T>(array: T[], element: T) {
+export function removeElement<T>(array: T[], element: T): void {
   const index = array.indexOf(element);
   if (index > -1) {
     array.splice(index, 1);
   }
 }
 
-export function isNotEmptyArray(array: any[] | undefined | null): boolean {
-  return !isEmptyArray(array);
-}
-
 export function isEmptyArray(array: any[] | undefined | null): boolean {
   return array === undefined || array === null || array.length === 0;
 }
 
-export function isPlainObject(value: any): boolean {
+export function isNotEmptyArray(array: any[] | undefined | null): boolean {
+  return !isEmptyArray(array);
+}
+
+export function isPlainObject(value: Record<string, any>): boolean {
   return value.constructor === Object;
 }
diff --git a/tools/src/index-generator.ts b/tools/src/index-generator.ts
index 591c376..855c51f 100644
--- a/tools/src/index-generator.ts
+++ b/tools/src/index-generator.ts
@@ -8,15 +8,15 @@ interface IReExport {
 const constParts = `export { Template } from "./core/template";
 `;
 
-function generate(paths: IReExport[]): string {
-  return constParts.concat(render(paths));
-}
-
 const render: (model: IReExport[]) => string = createTempate(`
 <#~ it :reExport #>export { <#= reExport.name #> } from "<#= reExport.path #>";
 <#~#>
 `.trim());
 
+function generate(paths: IReExport[]): string {
+  return constParts.concat(render(paths));
+}
+
 export default generate;
 export {
   IReExport,
diff --git a/tools/src/template.ts b/tools/src/template.ts
index 9552605..752ceb2 100644
--- a/tools/src/template.ts
+++ b/tools/src/template.ts
@@ -1,3 +1,5 @@
+/* eslint-disable no-control-regex */
+/* eslint-disable no-useless-escape */
 import { template, templateSettings } from 'dot';
 
 const defaultSettings = {
@@ -21,20 +23,20 @@ const createTempate = (templateStr: string): ((model: any) => string) => {
     .replace(/\x08/, '');
 };
 
-const L1: string = '\n' + tab(1);
-const L2: string = '\n' + tab(2);
-const L3: string = '\n' + tab(3);
-const L4: string = '\n' + tab(4);
+function tab(i: number): string {
+  return Array(i * 2 + 1).join(' ');
+}
+
+const L1 = `\n${tab(1)}`;
+const L2 = `\n${tab(2)}`;
+const L3 = `\n${tab(3)}`;
+const L4 = `\n${tab(4)}`;
 
 const TAB1: string = tab(1);
 const TAB2: string = tab(2);
 const TAB3: string = tab(3);
 const TAB4: string = tab(4);
 
-function tab(i: number): string {
-  return Array(i * 2 + 1).join(' ');
-}
-
 export {
   createTempate,
   L1,


Test patch:
diff --git a/src/core/__tests__/component.test.tsx b/src/core/__tests__/component.test.tsx
index 5060cd0..6e41538 100644
--- a/src/core/__tests__/component.test.tsx
+++ b/src/core/__tests__/component.test.tsx
@@ -1,10 +1,11 @@
 import * as events from 'devextreme/events';
 
 import { mount, React, shallow } from './setup';
-import { fireOptionChange, TestComponent, Widget, WidgetClass } from './test-component';
+import {
+  fireOptionChange, TestComponent, Widget, WidgetClass,
+} from './test-component';
 
 describe('rendering', () => {
-
   it('renders correctly', () => {
     const component = shallow(
       <TestComponent />,
@@ -51,7 +52,6 @@ describe('rendering', () => {
 });
 
 describe('element attrs management', () => {
-
   it('passes id, className and style to element', () => {
     const component = mount(
       <TestComponent id="id1" className="class1" style={{ background: 'red' }} />,
@@ -130,7 +130,6 @@ describe('element attrs management', () => {
 });
 
 describe('disposing', () => {
-
   it('call dispose', () => {
     const component = shallow(
       <TestComponent />,
@@ -164,5 +163,4 @@ describe('disposing', () => {
 
     expect(Widget.option.mock.calls.length).toBe(0);
   });
-
 });
diff --git a/src/core/__tests__/config.test.tsx b/src/core/__tests__/config.test.tsx
index 745208e..9b3a688 100644
--- a/src/core/__tests__/config.test.tsx
+++ b/src/core/__tests__/config.test.tsx
@@ -1,5 +1,5 @@
-import config, { getOption } from '../../core/config';
-import { mount, React} from './setup';
+import config, { getOption } from '../config';
+import { mount, React } from './setup';
 import { TestComponent, WidgetClass } from './test-component';
 
 class ComponentWithTemplates extends TestComponent {
@@ -41,7 +41,7 @@ describe('useLegacyTemplateEngine', () => {
       <ComponentWithTemplates itemRender={ItemTemplate} />,
     );
 
-    const render = WidgetClass.mock.calls[0][1].integrationOptions.templates.item.render;
+    const { render } = WidgetClass.mock.calls[0][1].integrationOptions.templates.item;
 
     render({
       container: document.createElement('div'),
@@ -54,7 +54,7 @@ describe('useLegacyTemplateEngine', () => {
 
   it('works for component template', () => {
     const ItemTemplate = (props: any) => {
-      const {value, dxkey} = props;
+      const { value, dxkey } = props;
       return (
         <div className="template">
           value:
@@ -71,7 +71,7 @@ describe('useLegacyTemplateEngine', () => {
       <ComponentWithTemplates itemComponent={ItemTemplate} />,
     );
 
-    const render = WidgetClass.mock.calls[0][1].integrationOptions.templates.item.render;
+    const { render } = WidgetClass.mock.calls[0][1].integrationOptions.templates.item;
 
     render({
       container: document.createElement('div'),
diff --git a/src/core/__tests__/dx-template.test.tsx b/src/core/__tests__/dx-template.test.tsx
index 70c876d..4493fe5 100644
--- a/src/core/__tests__/dx-template.test.tsx
+++ b/src/core/__tests__/dx-template.test.tsx
@@ -2,17 +2,37 @@ import { createDxTemplate } from '../dx-template';
 import { TemplatesStore } from '../templates-store';
 
 describe('dx-template', () => {
-
   describe('multiple rendering', () => {
     const container = {};
     const anotherContainer = {};
+    const templatesStore: any = {
+      add: jest.fn(),
+      remove: jest.fn(),
+      renderWrappers: jest.fn(),
+    };
+
+    function tryDoubleRender(model1: any, container1: any, model2: any, container2: any): void {
+      const template = createDxTemplate(
+        jest.fn(),
+        templatesStore as TemplatesStore,
+      );
+
+      template.render({
+        container: container1,
+        model: model1,
+      });
+
+      template.render({
+        container: container2,
+        model: model2,
+      });
+    }
 
     beforeEach(() => {
       jest.resetAllMocks();
     });
 
     describe('is prevented if', () => {
-
       it('is rendered with the same model', () => {
         const model: any = {};
 
@@ -38,11 +58,9 @@ describe('dx-template', () => {
         const secondId = templatesStore.add.mock.calls[1][0];
         expect(secondId).toBe(firstId);
       });
-
     });
 
     describe('is allowed if', () => {
-
       it('is rendered with different models', () => {
         // skip cases when model is undefined since no clear way of how to behave
         const model1: any = {};
@@ -97,28 +115,5 @@ describe('dx-template', () => {
         expect(secondId).not.toBe(firstId);
       });
     });
-
-    const templatesStore: any = {
-      add: jest.fn(),
-      remove: jest.fn(),
-      renderWrappers: jest.fn(),
-    };
-    function tryDoubleRender(model1: any, container1: any, model2: any, container2: any): void {
-      const template = createDxTemplate(
-        jest.fn(),
-        templatesStore as TemplatesStore,
-      );
-
-      template.render({
-        container: container1,
-        model: model1,
-      });
-
-      template.render({
-        container: container2,
-        model: model2,
-      });
-    }
   });
-
 });
diff --git a/src/core/__tests__/extension-component.test.tsx b/src/core/__tests__/extension-component.test.tsx
index e77c840..ed23794 100644
--- a/src/core/__tests__/extension-component.test.tsx
+++ b/src/core/__tests__/extension-component.test.tsx
@@ -1,12 +1,12 @@
-import { ExtensionComponent } from '../../core/extension-component';
-import ConfigurationComponent from '../../core/nested-option';
+/* eslint-disable max-classes-per-file */
+import { ExtensionComponent } from '../extension-component';
+import ConfigurationComponent from '../nested-option';
 import { mount, React, shallow } from './setup';
 import { TestComponent, Widget, WidgetClass } from './test-component';
 
 const ExtensionWidgetClass = jest.fn(() => Widget);
 
 class TestExtensionComponent<P = any> extends ExtensionComponent<P> {
-
   constructor(props: P) {
     super(props);
 
diff --git a/src/core/__tests__/nested-option.test.tsx b/src/core/__tests__/nested-option.test.tsx
index f479e97..7e481a3 100644
--- a/src/core/__tests__/nested-option.test.tsx
+++ b/src/core/__tests__/nested-option.test.tsx
@@ -1,5 +1,6 @@
-import { Component } from '../../core/component';
-import ConfigurationComponent from '../../core/nested-option';
+/* eslint-disable max-classes-per-file */
+import { Component } from '../component';
+import ConfigurationComponent from '../nested-option';
 import { mount, React } from './setup';
 import { TestComponent, Widget, WidgetClass } from './test-component';
 
@@ -60,7 +61,6 @@ class CollectionSubNestedComponent extends ConfigurationComponent<{ c?: number,
 }
 
 describe('nested option', () => {
-
   it('is pulled', () => {
     mount(
       <TestComponent>
@@ -162,9 +162,7 @@ describe('nested option', () => {
   });
 
   it('is pulled according to expectations', () => {
-
     class TestComponentWithExpectation<P = any> extends Component<P> {
-
       protected _expectedChildren = {
         option: {
           optionName: 'expectedItemOptions',
@@ -235,7 +233,8 @@ describe('nested option', () => {
         <CollectionNestedComponent key={1} c={123} d="abc" />,
         <CollectionNestedComponent key={2} c={999} />,
         <CollectionNestedComponent key={3} d="def" />,
-      ]});
+      ],
+    });
     jest.runAllTimers();
 
     expect(Widget.option.mock.calls.length).toBe(1);
@@ -243,10 +242,9 @@ describe('nested option', () => {
   });
 
   it('is pulled after update', () => {
-
     const TestContainer = (props: any) => {
-      const {value} = props;
-      return  (
+      const { value } = props;
+      return (
         <TestComponent>
           <NestedComponent a={value} />
         </TestComponent>
@@ -261,14 +259,13 @@ describe('nested option', () => {
   });
 
   it('is pulled after update without rubbish', () => {
-
     const component = mount(
       <TestComponent>
         <NestedComponent a={123} />
       </TestComponent>,
     );
 
-    component.setProps({ children: <NestedComponent a={456} />});
+    component.setProps({ children: <NestedComponent a={456} /> });
     jest.runAllTimers();
     expect(Widget.option.mock.calls.length).toBe(1);
     expect(Widget.option.mock.calls[0]).toEqual(['option.a', 456]);
@@ -276,7 +273,7 @@ describe('nested option', () => {
 
   it('updates widget option when collection item added', () => {
     const TestContainer = (props: any) => {
-      const {children} = props;
+      const { children } = props;
       const nesteds = children.map((child: any) => (
         <CollectionNestedComponent c={child.c} d={child.d} key={child.key} />
       ));
@@ -288,7 +285,7 @@ describe('nested option', () => {
       { c: 123, d: 'abc', key: 1 },
       { c: 456, d: 'def', key: 2 },
     ];
-    mount(<TestContainer children={children} />)
+    mount(<TestContainer>{children}</TestContainer>)
       .setProps({
         children: [
           { c: 123, d: 'abc', key: 1 },
@@ -308,7 +305,7 @@ describe('nested option', () => {
 
   it('updates widget option when collection item removed', () => {
     const TestContainer = (props: any) => {
-      const {children} = props;
+      const { children } = props;
       const nesteds = children.map((child: any) => (
         <CollectionNestedComponent c={child.c} d={child.d} key={child.key} />
       ));
@@ -320,7 +317,7 @@ describe('nested option', () => {
       { c: 123, d: 'abc', key: 1 },
       { c: 456, d: 'def', key: 2 },
     ];
-    mount(<TestContainer children={children} />)
+    mount(<TestContainer>{children}</TestContainer>)
       .setProps({
         children: [
           { c: 123, d: 'abc', key: 1 },
@@ -333,11 +330,9 @@ describe('nested option', () => {
       { c: 123, d: 'abc' },
     ]]);
   });
-
 });
 
 describe('nested sub-option', () => {
-
   it('is pulled', () => {
     mount(
       <TestComponent>
@@ -473,9 +468,8 @@ describe('nested sub-option', () => {
   });
 
   it('is pulled after update', () => {
-
     const TestContainer = (props: any) => {
-      const {value} = props;
+      const { value } = props;
       return (
         <TestComponent>
           <NestedComponent a={123}>
@@ -496,7 +490,6 @@ describe('nested sub-option', () => {
   });
 
   it('is pulled according to expectations', () => {
-
     class NestedComponentWithExpectations extends ConfigurationComponent<{ a: number }> {
       public static OptionName = 'option';
 
@@ -535,7 +528,7 @@ describe('nested sub-option', () => {
 });
 
 const ComponentWithConditionalOption = (props: {enableOption: boolean}) => {
-  const {enableOption} = props;
+  const { enableOption } = props;
   return (
     <TestComponent>
       {enableOption && <NestedComponent a={1} />}
@@ -549,10 +542,10 @@ describe('conditional rendering', () => {
       <ComponentWithConditionalOption enableOption={false} />,
     );
 
-    component.setProps({enableOption: true});
+    component.setProps({ enableOption: true });
 
     expect(Widget.option.mock.calls.length).toBe(1);
-    expect(Widget.option.mock.calls[0]).toEqual(['option', {a: 1}]);
+    expect(Widget.option.mock.calls[0]).toEqual(['option', { a: 1 }]);
   });
 
   it('removes option', () => {
@@ -560,7 +553,7 @@ describe('conditional rendering', () => {
       <ComponentWithConditionalOption enableOption />,
     );
 
-    component.setProps({enableOption: false});
+    component.setProps({ enableOption: false });
 
     expect(Widget.resetOption.mock.calls.length).toBe(1);
     expect(Widget.resetOption.mock.calls[0]).toEqual(['option']);
diff --git a/src/core/__tests__/props-updating.test.tsx b/src/core/__tests__/props-updating.test.tsx
index cbda7a7..f8af94b 100644
--- a/src/core/__tests__/props-updating.test.tsx
+++ b/src/core/__tests__/props-updating.test.tsx
@@ -1,5 +1,5 @@
-import { Component } from '../../core/component';
-import ConfigurationComponent from '../../core/nested-option';
+/* eslint-disable max-classes-per-file */
+import ConfigurationComponent from '../nested-option';
 import { mount, React, shallow } from './setup';
 import {
   eventHandlers,
@@ -19,7 +19,6 @@ interface IControlledComponentProps {
 }
 
 class ControlledComponent extends TestComponent<IControlledComponentProps> {
-
   protected _defaults = {
     defaultControlledOption: 'controlledOption',
   };
@@ -33,7 +32,6 @@ class NestedComponent extends ConfigurationComponent<{
   value?: number;
   onValueChange?: (value: number) => void;
 }> {
-
   public static DefaultsProps = {
     defaultC: 'c',
   };
@@ -61,20 +59,16 @@ class CollectionSubNestedComponent extends ConfigurationComponent<{
 (CollectionSubNestedComponent as any).OptionName = 'subItems';
 (CollectionSubNestedComponent as any).IsCollectionItem = true;
 
-class TestComponentWithExpectation<P = any> extends Component<P> {
-
+class TestComponentWithExpectation<P = any> extends TestComponent<P> {
   protected _expectedChildren = {
     items: {
       optionName: 'items',
       isCollectionItem: true,
     },
   };
-
-  protected _WidgetClass = WidgetClass;
 }
 
 describe('option update', () => {
-
   it('calls option method on props update', () => {
     const component = mount(
       <TestComponent />,
@@ -95,7 +89,7 @@ describe('option update', () => {
 
   it('updates nested collection item', () => {
     const TestContainer = (props: any) => {
-      const {value} = props;
+      const { value } = props;
       return (
         <TestComponentWithExpectation>
           <CollectionNestedComponent a={value} />
@@ -115,7 +109,7 @@ describe('option update', () => {
 
   it('updates sub-nested collection item', () => {
     const TestContainer = (props: any) => {
-      const {value} = props;
+      const { value } = props;
       return (
         <TestComponentWithExpectation>
           <CollectionNestedComponent>
@@ -133,11 +127,9 @@ describe('option update', () => {
     expect(Widget.option.mock.calls.length).toBe(1);
     expect(Widget.option.mock.calls[0]).toEqual(['items[0].subItems[0].a', 234]);
   });
-
 });
 
 describe('option control', () => {
-
   it('binds callback for optionChanged', () => {
     shallow(
       <ControlledComponent everyOption={123} />,
@@ -147,7 +139,6 @@ describe('option control', () => {
   });
 
   describe('handler option', () => {
-
     it('is not fired when option changed on props updating', () => {
       const handler = jest.fn();
       const component = shallow(
@@ -172,7 +163,6 @@ describe('option control', () => {
       Widget.option('controlledOption', 'controlled');
 
       expect(handler.mock.calls.length).toBe(1);
-
     });
 
     it('is not fired when option changed on props updating (handler updated)', () => {
@@ -224,7 +214,7 @@ describe('option control', () => {
     });
   });
 
-  [123, false, 0, undefined, null].map((value) => {
+  [123, false, 0, undefined, null].forEach((value) => {
     it('rolls back controlled simple option', () => {
       shallow(
         <ControlledComponent everyOption={value} />,
@@ -328,7 +318,6 @@ describe('option control', () => {
 });
 
 describe('option defaults control', () => {
-
   it('pass default values to widget', () => {
     shallow(
       <ControlledComponent defaultControlledOption="default" />,
@@ -358,11 +347,9 @@ describe('option defaults control', () => {
     jest.runAllTimers();
     expect(Widget.option.mock.calls.length).toBe(0);
   });
-
 });
 
 describe('cfg-component option control', () => {
-
   it('rolls cfg-component option value back', () => {
     mount(
       <ControlledComponent>
@@ -432,7 +419,7 @@ describe('cfg-component option control', () => {
 
   it('rolls cfg-component option value back if value has no changes', () => {
     const TestContainer = (props: any) => {
-      const {value} = props;
+      const { value } = props;
       return (
         <ControlledComponent>
           <NestedComponent a={value} b="const" />
@@ -453,7 +440,7 @@ describe('cfg-component option control', () => {
 
   it('apply cfg-component option change if value really change', () => {
     const TestContainer = (props: any) => {
-      const {value} = props;
+      const { value } = props;
       return (
         <ControlledComponent>
           <NestedComponent a={value} b="const" />
@@ -484,11 +471,9 @@ describe('cfg-component option control', () => {
     jest.runAllTimers();
     expect(Widget.option.mock.calls.length).toBe(0);
   });
-
 });
 
 describe('cfg-component option defaults control', () => {
-
   it('pass nested default values to widget', () => {
     mount(
       <ControlledComponent>
@@ -523,9 +508,8 @@ describe('cfg-component option defaults control', () => {
   });
 
   it('ignores 3rd-party changes in nested default props', () => {
-
     const TestContainer = (props: any) => {
-      const {optionDefValue} = props;
+      const { optionDefValue } = props;
       return (
         <ControlledComponent>
           <NestedComponent defaultC={optionDefValue} />
@@ -565,10 +549,22 @@ describe('cfg-component option defaults control', () => {
     expect(WidgetClass.mock.calls[0][1].nestedOption.c).toBe('controlled');
     expect(WidgetClass.mock.calls[0][1].nestedOption).not.toHaveProperty('defaultC');
   });
-
 });
 
 describe('mutation detection', () => {
+  const expectNoPropsUpdate = () => {
+    expect(Widget.option.mock.calls.length).toBe(0);
+    expect(Widget.beginUpdate.mock.calls.length).toBe(0);
+    expect(Widget.endUpdate.mock.calls.length).toBe(0);
+  };
+
+  const expectPropsUpdated = (expectedPath: string, value: any) => {
+    expect(Widget.option.mock.calls.length).toBe(1);
+    expect(Widget.beginUpdate.mock.calls.length).toBe(1);
+    expect(Widget.endUpdate.mock.calls.length).toBe(1);
+    expect(Widget.option.mock.calls[0][0]).toEqual(expectedPath);
+    expect(Widget.option.mock.calls[0][1]).toEqual(value);
+  };
 
   it('prevents update if no option changed', () => {
     const component = shallow(
@@ -625,24 +621,10 @@ describe('mutation detection', () => {
 
     expectPropsUpdated('anotherProp', 456);
   });
-
-  const expectNoPropsUpdate = () => {
-    expect(Widget.option.mock.calls.length).toBe(0);
-    expect(Widget.beginUpdate.mock.calls.length).toBe(0);
-    expect(Widget.endUpdate.mock.calls.length).toBe(0);
-  };
-
-  const expectPropsUpdated = (expectedPath: string, value: any) => {
-    expect(Widget.option.mock.calls.length).toBe(1);
-    expect(Widget.beginUpdate.mock.calls.length).toBe(1);
-    expect(Widget.endUpdate.mock.calls.length).toBe(1);
-    expect(Widget.option.mock.calls[0][0]).toEqual(expectedPath);
-    expect(Widget.option.mock.calls[0][1]).toEqual(value);
-  };
 });
 
 describe('onXXXChange', () => {
-  it('is called on component changes option', () => {
+  it('is called on component changes controlled option', () => {
     const onPropChange = jest.fn();
     const component = mount(
       <TestComponent
@@ -654,7 +636,7 @@ describe('onXXXChange', () => {
 
     const sampleProps = { text: '1' };
     component.setProps(sampleProps);
-
+    fireOptionChange('text', '1'); // controlled property calls the "onOptionChanged"
     expect(onPropChange).not.toBeCalled();
 
     fireOptionChange('text', '2');
@@ -662,6 +644,25 @@ describe('onXXXChange', () => {
     expect(onPropChange).toBeCalledWith('2');
   });
 
+  it('is not called on component changes controlled option using "onXXXChange"', () => {
+    const onPropChange = jest.fn();
+    const component = mount(
+      <TestComponent
+        text="0"
+        onTextChange={onPropChange}
+      />,
+    );
+    onPropChange.mockImplementation((value) => {
+      component.setProps({ text: `X${value}` });
+      fireOptionChange('text', `X${value}`);
+    });
+    expect(onPropChange).not.toBeCalled();
+
+    fireOptionChange('text', '2');
+    expect(onPropChange).toHaveBeenCalledTimes(1);
+    expect(component.prop('text')).toBe('X2');
+  });
+
   it('is called on component changes complex option', () => {
     const onPropChange = jest.fn();
     mount(
diff --git a/src/core/__tests__/template.test.tsx b/src/core/__tests__/template.test.tsx
index f4e733c..faee86b 100644
--- a/src/core/__tests__/template.test.tsx
+++ b/src/core/__tests__/template.test.tsx
@@ -1,31 +1,51 @@
+/* eslint-disable max-classes-per-file */
 import * as events from 'devextreme/events';
+import { Component } from '../component';
 
-import ConfigurationComponent from '../../core/nested-option';
-import { Template } from '../../core/template';
+import ConfigurationComponent from '../nested-option';
+import { Template } from '../template';
 import { mount, React, shallow } from './setup';
 import { TestComponent, Widget, WidgetClass } from './test-component';
 
+const templateProps = [{
+  tmplOption: 'item',
+  render: 'itemRender',
+  component: 'itemComponent',
+  keyFn: 'itemKeyFn',
+}];
+
 class ComponentWithTemplates extends TestComponent {
+  protected _templateProps = templateProps;
+}
 
-  protected _templateProps = [{
-    tmplOption: 'item',
-    render: 'itemRender',
-    component: 'itemComponent',
-    keyFn: 'itemKeyFn',
-  }];
+class ComponentWithAsyncTemplates<P> extends Component<P> {
+  protected _WidgetClass = WidgetClass;
+
+  protected _templateProps = templateProps;
 }
 
-function renderTemplate(name: string, model?: any, container?: any, index?: number, onRendered?: () => void): Element {
+function renderTemplate(
+  name: string,
+  model?: any,
+  container?: any,
+  index?: number,
+  onRendered?: () => void,
+): Element {
   model = model || {};
   container = container || document.createElement('div');
-  const render = WidgetClass.mock.calls[0][1].integrationOptions.templates[name].render;
+  const { render } = WidgetClass.mock.calls[0][1].integrationOptions.templates[name];
 
   return render({
     container, model, ...(index && { index }), onRendered,
   });
 }
 
-function renderItemTemplate(model?: any, container?: any, index?: number, onRendered?: () => void): Element {
+function renderItemTemplate(
+  model?: any,
+  container?: any,
+  index?: number,
+  onRendered?: () => void,
+): Element {
   return renderTemplate('item', model, container, index, onRendered);
 }
 
@@ -34,11 +54,9 @@ function testTemplateOption(testedOption: string) {
 
   if (testedOption === 'itemComponent') {
     prepareTemplate = (render) => {
-      class ItemComponent extends React.PureComponent {
-        public props: { data: any, index?: number };
-
+      class ItemComponent extends React.PureComponent<{data: any, index?: number}> {
         public render() {
-          const {data, index} = this.props;
+          const { data, index } = this.props;
           return render(data, index);
         }
       }
@@ -55,7 +73,7 @@ function testTemplateOption(testedOption: string) {
 
     expect(options.item).toBe('item');
 
-    const integrationOptions = options.integrationOptions;
+    const { integrationOptions } = options;
 
     expect(integrationOptions).toBeDefined();
     expect(integrationOptions.templates).toBeDefined();
@@ -152,9 +170,9 @@ function testTemplateOption(testedOption: string) {
     const component = mount(React.createElement(ComponentWithTemplates, elementOptions));
 
     const container = document.createElement('div');
-    renderItemTemplate({ text: 'with data'}, container);
+    renderItemTemplate({ text: 'with data' }, container);
     component.update();
-    expect(container.innerHTML).toBe('Template with data<span style=\"display: none;\"></span>');
+    expect(container.innerHTML).toBe('Template with data<span style="display: none;"></span>');
   });
 
   it('does not render template removeEvent listener', () => {
@@ -173,7 +191,7 @@ function testTemplateOption(testedOption: string) {
     const component = mount(React.createElement(ComponentWithTemplates, elementOptions));
 
     const container = document.createElement('table');
-    renderItemTemplate({ text: 'with data'}, container);
+    renderItemTemplate({ text: 'with data' }, container);
 
     component.update();
     expect(container.innerHTML)
@@ -263,7 +281,7 @@ function testTemplateOption(testedOption: string) {
     component.update();
     const removeListener = container.getElementsByTagName('SPAN')[0];
 
-    const parentElement = removeListener.parentElement;
+    const { parentElement } = removeListener;
     if (!parentElement) { throw new Error(); }
 
     parentElement.innerHTML = '';
@@ -290,7 +308,7 @@ function testTemplateOption(testedOption: string) {
     component.update();
     const templateContent = component.find('.template').getDOMNode();
 
-    const parentElement = templateContent.parentElement;
+    const { parentElement } = templateContent;
     if (!parentElement) { throw new Error(); }
 
     parentElement.removeChild(templateContent);
@@ -322,15 +340,13 @@ describe('function template', () => {
   });
 
   it('renders index', () => {
-    const itemRender: any = jest.fn((_, index: number) => {
-      return (
-        <div className="template">
-          Index
-          {' '}
-          {index}
-        </div>
-      );
-    });
+    const itemRender: any = jest.fn((_, index: number) => (
+      <div className="template">
+        Index
+        {' '}
+        {index}
+      </div>
+    ));
     const component = mount(
       <ComponentWithTemplates itemRender={itemRender} />,
     );
@@ -347,7 +363,7 @@ describe('component template', () => {
 
   it('renders index', () => {
     const ItemTemplate = (props: any) => {
-      const {data, index} = props;
+      const { data, index } = props;
       return (
         <div className="template">
           value:
@@ -387,7 +403,7 @@ describe('nested template', () => {
 
     expect(options.item).toBeUndefined();
 
-    const integrationOptions = options.integrationOptions;
+    const { integrationOptions } = options;
 
     expect(integrationOptions).toBeDefined();
     expect(integrationOptions.templates).toBeDefined();
@@ -484,7 +500,6 @@ describe('nested template', () => {
     expect(templates.length).toBe(1);
     expect(templates[0]).toBe('1');
   });
-
 });
 
 describe('component/render in nested options', () => {
@@ -529,7 +544,7 @@ describe('component/render in nested options', () => {
     const options = WidgetClass.mock.calls[0][1];
     expect(options.option.item).toBe('option.item');
 
-    const integrationOptions = options.integrationOptions;
+    const { integrationOptions } = options;
 
     expect(integrationOptions).toBeDefined();
     expect(integrationOptions.templates).toBeDefined();
@@ -548,7 +563,7 @@ describe('component/render in nested options', () => {
 
     const options = WidgetClass.mock.calls[0][1];
 
-    const integrationOptions = options.integrationOptions;
+    const { integrationOptions } = options;
 
     expect(integrationOptions.templates.nested).toBeDefined();
     expect(typeof integrationOptions.templates.nested.render).toBe('function');
@@ -574,7 +589,7 @@ describe('component/render in nested options', () => {
     expect(options.option.item).toBe('option.item');
     expect(options.collection[0].template).toBe('collection[0].template');
 
-    const integrationOptions = options.integrationOptions;
+    const { integrationOptions } = options;
 
     expect(Object.keys(integrationOptions.templates)).toEqual(['option.item', 'collection[0].template']);
   });
@@ -605,7 +620,7 @@ describe('component/render in nested options', () => {
     expect(options.collection[2].option.item).toBe('collection[2].option.item');
     expect(options.option.collection[0].template).toBe('option.collection[0].template');
 
-    const integrationOptions = options.integrationOptions;
+    const { integrationOptions } = options;
 
     expect(Object.keys(integrationOptions.templates)).toEqual([
       'option.collection[0].template',
@@ -667,7 +682,7 @@ describe('component/render in nested options', () => {
     expect(options.option.item).toBe(undefined);
     expect(options.option.template).toBe(undefined);
 
-    const integrationOptions = options.integrationOptions;
+    const { integrationOptions } = options;
 
     expect(Object.keys(integrationOptions.templates)).toEqual([
       'collection[0].template',
@@ -725,7 +740,7 @@ describe('component/render in nested options', () => {
   it('renders updates in deeply nested templates', () => {
     const getTemplate = (arg: string) => () => <div className="template">{arg}</div>;
     const TestContainer = (props: any) => {
-      const {value} = props;
+      const { value } = props;
       return (
         <TestComponent>
           <CollectionNestedComponent>
@@ -735,7 +750,7 @@ describe('component/render in nested options', () => {
       );
     };
     const component = mount(<TestContainer value="test" />);
-    component.setProps({value: 'test2'});
+    component.setProps({ value: 'test2' });
     jest.runAllTimers();
 
     renderTemplate('collection[0].option.item');
@@ -745,13 +760,15 @@ describe('component/render in nested options', () => {
 
   it('adds nested components dynamically', () => {
     const renderItem = () => <div>Template</div>;
-    const items = [{id: 1, render: renderItem}];
+    const items = [{ id: 1, render: renderItem }];
 
     const TestContainer = (props: any) => {
-      const {items} = props;
+      const { items: propItems } = props;
       return (
         <TestComponent>
-          {items.map((item) => <CollectionNestedComponent key={item.id} render={item.render} />)}
+          {propItems.map(
+            (item) => <CollectionNestedComponent key={item.id} render={item.render} />,
+          )}
         </TestComponent>
       );
     };
@@ -760,7 +777,7 @@ describe('component/render in nested options', () => {
     component.setProps({
       items: [
         ...items,
-        {id: 2, render: renderItem},
+        { id: 2, render: renderItem },
       ],
     });
 
@@ -780,13 +797,15 @@ describe('component/render in nested options', () => {
 
   it('removes nested components dynamically', () => {
     const renderItem = () => <div>Template</div>;
-    const items = [{id: 1, render: renderItem}, {id: 2, render: renderItem}];
+    const items = [{ id: 1, render: renderItem }, { id: 2, render: renderItem }];
 
     const TestContainer = (props: any) => {
-      const {items} = props;
+      const { items: propItems } = props;
       return (
         <TestComponent>
-          {items.map((item) => <CollectionNestedComponent key={item.id} render={item.render} />)}
+          {propItems.map(
+            (item) => <CollectionNestedComponent key={item.id} render={item.render} />,
+          )}
         </TestComponent>
       );
     };
@@ -805,13 +824,15 @@ describe('component/render in nested options', () => {
 
   xit('removes deleted tempalates from integrationOptions', () => {
     const ItemTemplate = () => <div>Template</div>;
-    const items = [{id: 1, render: ItemTemplate}, {id: 2, render: ItemTemplate}];
+    const items = [{ id: 1, render: ItemTemplate }, { id: 2, render: ItemTemplate }];
 
     const TestContainer = (props: any) => {
-      const {items} = props;
+      const { items: propItems } = props;
       return (
         <TestComponent>
-          {items.map((item) => <CollectionNestedComponent key={item.id} render={item.render} />)}
+          {propItems.map(
+            (item) => <CollectionNestedComponent key={item.id} render={item.render} />,
+          )}
         </TestComponent>
       );
     };
@@ -846,7 +867,57 @@ describe('component/render in nested options', () => {
       </ComponentWithTranscludedContent>,
     );
 
-    const integrationOptions = WidgetClass.mock.calls[0][1].integrationOptions;
+    const { integrationOptions } = WidgetClass.mock.calls[0][1];
     expect(integrationOptions).toBe(undefined);
   });
 });
+
+describe('async template', () => {
+  const waitForceUpdateFromTemplateRenderer = () => new Promise((ok) => requestAnimationFrame(ok));
+
+  it('renders', async () => {
+    const elementOptions: Record<string, any> = {};
+    elementOptions.itemRender = (data: any) => (
+      <div className="template">
+        Template
+        {' '}
+        {data.text}
+      </div>
+    );
+
+    const component = mount(React.createElement(ComponentWithAsyncTemplates, elementOptions));
+    renderItemTemplate({ text: 'with data' });
+
+    expect(component.find('.template').length).toBe(0);
+
+    await waitForceUpdateFromTemplateRenderer();
+    component.update();
+
+    expect(component.find('.template').html()).toBe('<div class="template">Template with data</div>');
+  });
+
+  it('does not force update on each template', async () => {
+    const elementOptions: Record<string, any> = {};
+    elementOptions.itemRender = (data: any) => (
+      <div className="template">
+        Template
+        {data.text}
+      </div>
+    );
+
+    const component = mount(React.createElement(ComponentWithAsyncTemplates, elementOptions));
+    const componentInstance = component.instance() as any;
+    const renderSpy = jest.spyOn(componentInstance._templatesStore, 'renderWrappers');
+
+    renderItemTemplate({ text: 'with data1' });
+    renderItemTemplate({ text: 'with data2' });
+
+    expect(renderSpy.mock.calls.length).toBe(0);
+
+    await waitForceUpdateFromTemplateRenderer();
+    component.update();
+
+    expect(renderSpy.mock.calls.length).toBe(1);
+    renderSpy.mockRestore();
+  });
+});
diff --git a/src/core/__tests__/templates-store.test.tsx b/src/core/__tests__/templates-store.test.tsx
index de4fb79..ba27d1c 100644
--- a/src/core/__tests__/templates-store.test.tsx
+++ b/src/core/__tests__/templates-store.test.tsx
@@ -1,7 +1,6 @@
 import { TemplatesStore } from '../templates-store';
 
 describe('templates-store', () => {
-
   it('adds', () => {
     const templatesStore = new TemplatesStore(jest.fn());
 
diff --git a/src/core/__tests__/test-component.ts b/src/core/__tests__/test-component.ts
index 859c9ec..0441807 100644
--- a/src/core/__tests__/test-component.ts
+++ b/src/core/__tests__/test-component.ts
@@ -1,4 +1,4 @@
-import { Component } from '../../core/component';
+import { Component } from '../component';
 
 const eventHandlers: { [index: string]: (e?: any) => void } = {};
 
@@ -7,7 +7,7 @@ const Widget = {
   resetOption: jest.fn(),
   beginUpdate: jest.fn(),
   endUpdate: jest.fn(),
-  on: (event: string, handler: (e: any) => void) => {
+  on: (event: string, handler: (e: any) => void): void => {
     eventHandlers[event] = handler;
   },
   dispose: jest.fn(),
@@ -17,9 +17,16 @@ const WidgetClass = jest.fn(() => Widget);
 
 class TestComponent<P = any> extends Component<P> {
   protected _WidgetClass = WidgetClass;
+
+  _createWidget(element?: Element): void {
+    Widget.option.mockImplementation((name: string) => name === 'integrationOptions.useDeferUpdateForTemplates');
+
+    super._createWidget(element);
+    Widget.option.mockReset();
+  }
 }
 
-function fireOptionChange(fullName: string, value: any) {
+function fireOptionChange(fullName: string, value: unknown): void {
   eventHandlers.optionChanged({
     name: fullName.split('.')[0],
     fullName,
diff --git a/src/core/configuration/__tests__/comparer.test.ts b/src/core/configuration/__tests__/comparer.test.ts
index 552aa2c..bede18a 100644
--- a/src/core/configuration/__tests__/comparer.test.ts
+++ b/src/core/configuration/__tests__/comparer.test.ts
@@ -23,14 +23,14 @@ describe('child config nodes comparing', () => {
         option: {
           ...emptyNode,
           fullName: 'option',
-          options: {a: 1},
+          options: { a: 1 },
         },
       },
     };
 
     const changes = getChanges(currentConfig, prevConfig);
     expect(Object.keys(changes.options).length).toEqual(1);
-    expect(changes.options.option).toEqual({a: 1});
+    expect(changes.options.option).toEqual({ a: 1 });
   });
 });
 
@@ -91,6 +91,6 @@ describe('collections comparing', () => {
 
     const changes = getChanges(currentConfig, prevConfig);
     expect(Object.keys(changes.options).length).toEqual(1);
-    expect(changes.options['items[0].items[0].items']).toEqual([ {a: 11}, {b: 22}, {c: 33} ]);
+    expect(changes.options['items[0].items[0].items']).toEqual([{ a: 11 }, { b: 22 }, { c: 33 }]);
   });
 });
diff --git a/src/core/configuration/react/__tests__/element.test.ts b/src/core/configuration/react/__tests__/element.test.ts
index 6106a45..6bff365 100644
--- a/src/core/configuration/react/__tests__/element.test.ts
+++ b/src/core/configuration/react/__tests__/element.test.ts
@@ -1,6 +1,7 @@
+/* eslint-disable max-classes-per-file */
 import { mount, React } from '../../../__tests__/setup';
 
-import ConfigurationComponent from '../../../../core/nested-option';
+import ConfigurationComponent from '../../../nested-option';
 import { Template } from '../../../template';
 
 import { ElementType, getElementInfo } from '../element';
@@ -58,7 +59,7 @@ const otherComponents = [
 ];
 
 describe('getElementInfo', () => {
-  configurationComponents.map((component) => {
+  configurationComponents.forEach((component) => {
     it('parses Configuration component', () => {
       const wrapper = mount(React.createElement(component));
       const elementInfo = getElementInfo(wrapper.getElement());
@@ -70,7 +71,7 @@ describe('getElementInfo', () => {
 
       expect(elementInfo.props).toEqual(wrapper.getElement().props);
 
-      const descriptor = elementInfo.descriptor;
+      const { descriptor } = elementInfo;
       expect(descriptor.name).toEqual(component.OptionName);
       expect(descriptor.isCollection).toEqual(component.IsCollectionItem);
       expect(descriptor.templates).toEqual(component.TemplateProps || []);
@@ -99,7 +100,7 @@ describe('getElementInfo', () => {
     expect(elementInfo.props).toEqual(wrapper.getElement().props);
   });
 
-  otherComponents.map((component) => {
+  otherComponents.forEach((component) => {
     it('parses Other components', () => {
       const wrapper = mount(React.createElement(component));
       const elementInfo = getElementInfo(wrapper.getElement());
diff --git a/src/core/configuration/react/__tests__/templates.test.ts b/src/core/configuration/react/__tests__/templates.test.ts
index 5e0fae0..cf0357e 100644
--- a/src/core/configuration/react/__tests__/templates.test.ts
+++ b/src/core/configuration/react/__tests__/templates.test.ts
@@ -106,7 +106,7 @@ const negativeCasesForAnonymous = [
 ];
 
 describe('getAnonymousTemplate', () => {
-  positiveCasesForAnonymous.map(
+  positiveCasesForAnonymous.forEach(
     (testCase) => {
       it('returns template', () => {
         const template = getAnonymousTemplate(
@@ -129,7 +129,7 @@ describe('getAnonymousTemplate', () => {
     },
   );
 
-  negativeCasesForAnonymous.map(
+  negativeCasesForAnonymous.forEach(
     (testCase) => {
       it('returns null', () => {
         const template = getAnonymousTemplate(
@@ -241,7 +241,7 @@ const casesForNamed = [
 ];
 
 describe('getNamedTemplate', () => {
-  casesForNamed.map(
+  casesForNamed.forEach(
     (testCase) => {
       it('returns template', () => {
         const template = getNamedTemplate(testCase.props as ITemplateProps);
diff --git a/tools/src/component-generator.test.ts b/tools/src/component-generator.test.ts
index 28743eb..352f660 100644
--- a/tools/src/component-generator.test.ts
+++ b/tools/src/component-generator.test.ts
@@ -1,7 +1,7 @@
 import generate from './component-generator';
 
 it('generates', () => {
-  //#region EXPECTED
+  // #region EXPECTED
   const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -26,7 +26,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
   expect(
     generate({
@@ -39,7 +39,7 @@ export {
 });
 
 it('generates extension component', () => {
-  //#region EXPECTED
+  // #region EXPECTED
   const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions as ICLASS_NAMEOptions
@@ -61,7 +61,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
   expect(
     generate({
@@ -75,9 +75,8 @@ export {
 });
 
 describe('template-props generation', () => {
-
   it('processes option', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -112,7 +111,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -126,7 +125,7 @@ export {
   });
 
   it('processes several options', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -169,7 +168,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -183,7 +182,7 @@ export {
   });
 
   it('processes single widget-template option', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -218,7 +217,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -233,9 +232,8 @@ export {
 });
 
 describe('props generation', () => {
-
   it('processes subscribable option', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -266,7 +264,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -282,7 +280,7 @@ export {
   });
 
   it('processes several subscribable options', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -316,7 +314,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -334,9 +332,8 @@ export {
 });
 
 describe('nested options', () => {
-
   it('processes nested options', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -392,7 +389,7 @@ export {
   IOpt_6_SubComponentProps
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -404,7 +401,7 @@ export {
         nestedComponents: [
           {
             className: 'Opt_1_Component',
-            owners: [ 'CLASS_NAME' ],
+            owners: ['CLASS_NAME'],
             optionName: 'opt_1',
             options: [
               {
@@ -433,7 +430,7 @@ export {
           },
           {
             className: 'Opt_6_SubComponent',
-            owners: [ 'Opt_1_Component' ],
+            owners: ['Opt_1_Component'],
             optionName: 'sub_sub_opt_7',
             options: [
               {
@@ -448,7 +445,7 @@ export {
   });
 
   it('processes nested array option', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -488,7 +485,7 @@ export {
   IOpt_1_ComponentProps
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -500,7 +497,7 @@ export {
         nestedComponents: [
           {
             className: 'Opt_1_Component',
-            owners: [ 'CLASS_NAME' ],
+            owners: ['CLASS_NAME'],
             optionName: 'opt_1',
             isCollectionItem: true,
             options: [
@@ -516,7 +513,7 @@ export {
   });
 
   it('generates default props for nested options', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -563,7 +560,7 @@ export {
   IOpt_1_ComponentProps
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -575,7 +572,7 @@ export {
         nestedComponents: [
           {
             className: 'Opt_1_Component',
-            owners: [ 'CLASS_NAME' ],
+            owners: ['CLASS_NAME'],
             optionName: 'opt_1',
             options: [
               {
@@ -601,7 +598,7 @@ export {
   });
 
   it('generates component/render props for nested options', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -649,7 +646,7 @@ export {
   IOpt_1_ComponentProps
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -661,7 +658,7 @@ export {
         nestedComponents: [
           {
             className: 'Opt_1_Component',
-            owners: [ 'CLASS_NAME' ],
+            owners: ['CLASS_NAME'],
             optionName: 'opt_1',
             templates: ['optionTemplate'],
             options: [
@@ -677,7 +674,7 @@ export {
   });
 
   it('processes nested option predefined props', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -720,7 +717,7 @@ export {
   IOpt_1_ComponentProps
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -732,7 +729,7 @@ export {
         nestedComponents: [
           {
             className: 'Opt_1_Component',
-            owners: [ 'CLASS_NAME' ],
+            owners: ['CLASS_NAME'],
             optionName: 'opt_1',
             isCollectionItem: true,
             predefinedProps: {
@@ -750,7 +747,7 @@ export {
     ).toBe(EXPECTED);
   });
   it('renders [] if nested suboption has array type', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -797,7 +794,7 @@ export {
   IOpt_1_ComponentProps
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -842,9 +839,8 @@ export {
 });
 
 describe('prop typings', () => {
-
   it('adds check for single type', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -873,7 +869,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -892,7 +888,7 @@ export {
   });
 
   it('adds check for acceptable values', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -924,7 +920,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -944,7 +940,7 @@ export {
   });
 
   it('adds check for several types', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -976,7 +972,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -995,7 +991,7 @@ export {
   });
 
   it('adds typings in alphabetic order', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -1030,7 +1026,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -1062,9 +1058,8 @@ export {
 });
 
 describe('child expectation', () => {
-
   it('is rendered for widget', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -1094,7 +1089,7 @@ export {
   ICLASS_NAMEOptions
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -1111,7 +1106,7 @@ export {
   });
 
   it('is rendered for nested option', () => {
-    //#region EXPECTED
+    // #region EXPECTED
     const EXPECTED = `
 import dxCLASS_NAME, {
     IOptions
@@ -1153,7 +1148,7 @@ export {
   IOpt_1_ComponentProps
 };
 `.trimLeft();
-    //#endregion
+    // #endregion
 
     expect(
       generate({
@@ -1165,7 +1160,7 @@ export {
         nestedComponents: [
           {
             className: 'Opt_1_Component',
-            owners: [ 'CLASS_NAME' ],
+            owners: ['CLASS_NAME'],
             optionName: 'opt_1',
             options: [],
             expectedChildren: [
@@ -1184,5 +1179,4 @@ export {
       }),
     ).toBe(EXPECTED);
   });
-
 });
diff --git a/tools/src/converter.test.ts b/tools/src/converter.test.ts
index 44b38ad..55c00c3 100644
--- a/tools/src/converter.test.ts
+++ b/tools/src/converter.test.ts
@@ -62,8 +62,8 @@ it('expands custom types', () => {
       CustomType: {
         name: 'CustomType',
         types: [
-          { type: 'String', isCustomType: false, acceptableValues: []},
-          { type: 'Number', isCustomType: false, acceptableValues: []},
+          { type: 'String', isCustomType: false, acceptableValues: [] },
+          { type: 'Number', isCustomType: false, acceptableValues: [] },
         ],
         props: [],
         templates: [],
diff --git a/tools/src/index-generator.test.ts b/tools/src/index-generator.test.ts
index ef14a49..656e768 100644
--- a/tools/src/index-generator.test.ts
+++ b/tools/src/index-generator.test.ts
@@ -1,5 +1,12 @@
 import generate from './index-generator';
 
+// #region EXPECTED_GENERATES
+const EXPECTED_GENERATES = `
+export { Template } from "./core/template";
+export { widget } from "./path";
+export { anotherWidget } from "./another/path";
+`.trimLeft();
+
 it('generates', () => {
   expect(
     generate([
@@ -8,9 +15,3 @@ it('generates', () => {
     ]),
   ).toBe(EXPECTED_GENERATES);
 });
-//#region EXPECTED_GENERATES
-const EXPECTED_GENERATES = `
-export { Template } from "./core/template";
-export { widget } from "./path";
-export { anotherWidget } from "./another/path";
-`.trimLeft();