Skip to content

Commit e263e39

Browse files
optimize template rendering after custom configuration components feature
1 parent 2932f32 commit e263e39

File tree

11 files changed

+131
-64
lines changed

11 files changed

+131
-64
lines changed

packages/devextreme-react/src/core/__tests__/component.test.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
Widget,
1414
WidgetClass,
1515
} from './test-component';
16-
import { TemplateDiscoveryContext } from '../contexts';
16+
import { TemplateRenderingContext } from '../contexts';
1717

1818

1919
jest.useFakeTimers();
@@ -149,7 +149,7 @@ describe('rendering', () => {
149149
testingLib.configure({ reactStrictMode: true });
150150

151151
const InnerComponent = () => {
152-
const { discoveryRendering: isTemplateTested } = React.useContext(TemplateDiscoveryContext);
152+
const { isTemplateRendering: isTemplateTested } = React.useContext(TemplateRenderingContext);
153153

154154
return (
155155
<TestComponent isTemplateTested={isTemplateTested}>
@@ -206,7 +206,7 @@ describe('rendering', () => {
206206
expect(createPortalFn.mock.calls.some(call => {
207207
const reactElement = call[0] as unknown as React.ReactElement;
208208

209-
return reactElement.type !== TemplateDiscoveryContext.Provider
209+
return reactElement.type !== TemplateRenderingContext.Provider
210210
})).toBeFalsy();
211211
});
212212

@@ -235,7 +235,7 @@ describe('rendering', () => {
235235
expect(createPortalFn.mock.calls.filter(call => {
236236
const reactElement = call[0] as unknown as React.ReactElement;
237237

238-
return reactElement.type !== TemplateDiscoveryContext.Provider
238+
return reactElement.type !== TemplateRenderingContext.Provider
239239
}).length).toEqual(1);
240240
});
241241

@@ -260,8 +260,8 @@ describe('rendering', () => {
260260
</TestComponent>,
261261
);
262262

263-
expect(WidgetClass.mock.instances.length).toBe(3);
264-
expect(WidgetClass.mock.instances[2]).toEqual({});
263+
expect(WidgetClass.mock.instances.length).toBe(2);
264+
expect(WidgetClass.mock.instances[1]).toEqual({});
265265
});
266266

267267
it('clears nested option in strict mode', () => {
@@ -272,7 +272,7 @@ describe('rendering', () => {
272272
</TestComponent>
273273
</React.StrictMode>,
274274
);
275-
expect(Widget.clearExtensions).toHaveBeenCalledTimes(6);
275+
expect(Widget.clearExtensions).toHaveBeenCalledTimes(4);
276276
});
277277

278278
it('do not pass children to options', () => {

packages/devextreme-react/src/core/__tests__/extension-component.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ it('creates widget on componentDidMount inside another component on same element
7070
</TestComponent>,
7171
);
7272

73-
expect(ExtensionWidgetClass).toHaveBeenCalledTimes(2);
74-
expect(ExtensionWidgetClass.mock.calls[1][0]).toBe(WidgetClass.mock.calls[0][0]);
73+
expect(ExtensionWidgetClass).toHaveBeenCalledTimes(1);
7574
});
7675

7776
it('unmounts without errors', () => {
@@ -91,7 +90,7 @@ it('pulls options from a single nested component', () => {
9190
</TestComponent>,
9291
);
9392

94-
const options = ExtensionWidgetClass.mock.calls[1][1];
93+
const options = ExtensionWidgetClass.mock.calls[0][1];
9594

9695
expect(options).toHaveProperty('option1');
9796
expect(options.option1).toMatchObject({

packages/devextreme-react/src/core/__tests__/integration/integration.test.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import * as testingLib from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event'
23
import * as React from 'react';
34

45
import { useState, useEffect } from 'react';
56
import SelectBox from '../../../select-box';
67
import TextBox from '../../../text-box';
8+
import { ContextMenu, Item as ContextMenuItem } from '../../../context-menu';
79

810
jest.useFakeTimers();
911

10-
describe('selectbox', () => {
12+
describe('integration tests', () => {
1113
afterEach(() => {
1214
jest.clearAllMocks();
1315
testingLib.cleanup();
@@ -45,4 +47,37 @@ describe('selectbox', () => {
4547

4648
expect(renderSelectBox).not.toThrow();
4749
});
50+
51+
it('context menu renders items specified as config components', async () => {
52+
const user = userEvent.setup({ delay: null });
53+
54+
const renderContextMenu = testingLib.render(
55+
<React.StrictMode>
56+
<div className='my-context-menu-target'>Right-click me!</div>
57+
<ContextMenu target='.my-context-menu-target'>
58+
<ContextMenuItem text='Category 1'>
59+
<ContextMenuItem>
60+
<div>Item 1</div>
61+
</ContextMenuItem>
62+
<ContextMenuItem>
63+
<div>Item 2</div>
64+
</ContextMenuItem>
65+
<ContextMenuItem>
66+
<div>Item 3</div>
67+
</ContextMenuItem>
68+
</ContextMenuItem>
69+
</ContextMenu>
70+
</React.StrictMode>
71+
);
72+
73+
await user.pointer({ target: testingLib.screen.getByText('Right-click me!'), keys: '[MouseRight]' });
74+
75+
const categoryMenuItemElement = await renderContextMenu.findByText('Category 1');
76+
expect(categoryMenuItemElement).not.toBeFalsy();
77+
78+
await user.hover(testingLib.screen.getByText('Category 1'));
79+
const firstMenuSubItemElement = await renderContextMenu.findByText('Item 1');
80+
81+
expect(firstMenuSubItemElement).not.toBeFalsy();
82+
});
4883
});

packages/devextreme-react/src/core/__tests__/nested-option.test.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const NestedComponent = function NestedComponent(props: any) {
1313
<ConfigurationComponent<{ a: number } & React.PropsWithChildren>
1414
elementDescriptor={{
1515
OptionName: 'option',
16+
ExpectedChildren: {
17+
test: { optionName: "test", isCollectionItem: false },
18+
},
1619
}}
1720
{...props}
1821
/>
@@ -29,6 +32,9 @@ const NestedComponentWithPredfeinedProps = function NestedComponentWithPredfeine
2932
PredefinedProps: {
3033
predefinedProp: 'predefined-value',
3134
},
35+
ExpectedChildren: {
36+
test: { optionName: "test", isCollectionItem: false },
37+
},
3238
}}
3339
{...props}
3440
/>
@@ -46,6 +52,9 @@ const CollectionNestedWithPredfeinedProps1 = function CollectionNestedWithPredfe
4652
PredefinedProps: {
4753
predefinedProp: 'predefined-value-1',
4854
},
55+
ExpectedChildren: {
56+
test: { optionName: "test", isCollectionItem: false },
57+
},
4958
}}
5059
{...props}
5160
/>
@@ -63,6 +72,9 @@ const CollectionNestedWithPredfeinedProps2 = function CollectionNestedWithPredfe
6372
PredefinedProps: {
6473
predefinedProp: 'predefined-value-2',
6574
},
75+
ExpectedChildren: {
76+
test: { optionName: "test", isCollectionItem: false },
77+
},
6678
}}
6779
{...props}
6880
/>
@@ -76,6 +88,9 @@ const SubNestedComponent = function SubNestedComponent(props: any) {
7688
<ConfigurationComponent<{ d: string }>
7789
elementDescriptor={{
7890
OptionName: 'subOption',
91+
ExpectedChildren: {
92+
test: { optionName: "test", isCollectionItem: false },
93+
},
7994
}}
8095
{...props}
8196
/>
@@ -89,6 +104,9 @@ const AnotherSubNestedComponent = function AnotherSubNestedComponent(props: any)
89104
<ConfigurationComponent<{ e: string }>
90105
elementDescriptor={{
91106
OptionName: 'anotherSubOption',
107+
ExpectedChildren: {
108+
test: { optionName: "test", isCollectionItem: false },
109+
},
92110
}}
93111
{...props}
94112
/>
@@ -102,6 +120,9 @@ const AnotherNestedComponent = function AnotherNestedComponent(props: any) {
102120
<ConfigurationComponent<{ b: string }>
103121
elementDescriptor={{
104122
OptionName: 'anotherOption',
123+
ExpectedChildren: {
124+
test: { optionName: "test", isCollectionItem: false },
125+
},
105126
}}
106127
{...props}
107128
/>
@@ -116,6 +137,9 @@ const CollectionNestedComponent = function CollectionNestedComponent(props: any)
116137
elementDescriptor={{
117138
OptionName: 'itemOptions',
118139
IsCollectionItem: true,
140+
ExpectedChildren: {
141+
test: { optionName: "test", isCollectionItem: false },
142+
},
119143
}}
120144
{...props}
121145
/>
@@ -130,6 +154,9 @@ const CollectionSubNestedComponent = function CollectionSubNestedComponent(props
130154
elementDescriptor={{
131155
OptionName: 'subItemsOptions',
132156
IsCollectionItem: true,
157+
ExpectedChildren: {
158+
test: { optionName: "test", isCollectionItem: false },
159+
},
133160
}}
134161
{...props}
135162
/>
@@ -201,7 +228,7 @@ describe('nested option', () => {
201228
expect(Widget.option.mock.calls[0][1]).toEqual({ a: 345 });
202229
});
203230

204-
it('is pulled form nested component', () => {
231+
it('is pulled from nested component', () => {
205232
render(
206233
<TestComponent
207234
templateProps={[{
@@ -237,7 +264,7 @@ describe('nested option', () => {
237264
</TestComponent>,
238265
);
239266

240-
expect(WidgetClass.mock.calls[3][1]).toEqual({
267+
expect(WidgetClass.mock.calls[2][1]).toEqual({
241268
templatesRenderAsynchronously: true,
242269
option: {
243270
a: 345,

packages/devextreme-react/src/core/__tests__/template.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,9 @@ describe('component/render in nested options', () => {
576576
render: 'itemRender',
577577
component: 'itemComponent',
578578
}],
579+
ExpectedChildren: {
580+
test: { optionName: "test", isCollectionItem: false },
581+
},
579582
}}
580583
{...props}
581584
/>
@@ -599,6 +602,9 @@ describe('component/render in nested options', () => {
599602
render: 'render',
600603
component: 'component',
601604
}],
605+
ExpectedChildren: {
606+
test: { optionName: "test", isCollectionItem: false },
607+
},
602608
}}
603609
{...props}
604610
/>

packages/devextreme-react/src/core/component-base.tsx

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
useLayoutEffect,
1010
useCallback,
1111
useState,
12-
useMemo,
1312
ReactElement,
1413
} from 'react';
1514

@@ -31,7 +30,7 @@ import {
3130
NestedOptionContext,
3231
RemovalLockerContext,
3332
RestoreTreeContext,
34-
TemplateDiscoveryContext,
33+
TemplateRenderingContext,
3534
} from './contexts';
3635

3736
const DX_REMOVE_EVENT = 'dxremove';
@@ -94,13 +93,9 @@ const ComponentBase = forwardRef<ComponentBaseRef, any>(
9493
const updateTemplates = useRef<(callback: () => void) => void>();
9594

9695
const prevPropsRef = useRef<P & ComponentBaseProps>();
96+
const childrenContainer = useRef<HTMLDivElement>(null);
9797

98-
const templateContainer = useMemo(
99-
() => (typeof document !== 'undefined'
100-
? document.createElement('div')
101-
: undefined),
102-
[],
103-
);
98+
const { parentType } = useContext(NestedOptionContext);
10499

105100
const [widgetConfig, context] = useOptionScanning(
106101
{
@@ -116,8 +111,9 @@ const ComponentBase = forwardRef<ComponentBaseRef, any>(
116111
props,
117112
},
118113
props.children,
119-
templateContainer,
114+
childrenContainer,
120115
Symbol('initial update token'),
116+
'component',
121117
);
122118

123119
const restoreTree = useCallback(() => {
@@ -282,6 +278,10 @@ const ComponentBase = forwardRef<ComponentBaseRef, any>(
282278
}, [shouldRestoreFocus.current, instance.current]);
283279

284280
const onComponentUpdated = useCallback(() => {
281+
if (parentType === 'option') {
282+
return;
283+
}
284+
285285
if (!optionsManager.current?.isInstanceSet) {
286286
return;
287287
}
@@ -306,6 +306,10 @@ const ComponentBase = forwardRef<ComponentBaseRef, any>(
306306
]);
307307

308308
const onComponentMounted = useCallback(() => {
309+
if (parentType === 'option') {
310+
return;
311+
}
312+
309313
const { style } = props;
310314

311315
if (childElementsDetached.current) {
@@ -426,16 +430,10 @@ const ComponentBase = forwardRef<ComponentBaseRef, any>(
426430

427431
return (
428432
<RestoreTreeContext.Provider value={restoreTree}>
429-
<TemplateDiscoveryContext.Provider value={{ discoveryRendering: false }}>
430-
{ templateContainer
431-
&& createPortal(
432-
<TemplateDiscoveryContext.Provider value={{ discoveryRendering: true }}>
433-
{_renderChildren()}
434-
</TemplateDiscoveryContext.Provider>,
435-
templateContainer,
436-
)
437-
}
438-
<div {...getElementProps()}>
433+
<TemplateRenderingContext.Provider value={{
434+
isTemplateRendering: false,
435+
}}>
436+
<div ref={childrenContainer} {...getElementProps()}>
439437
<NestedOptionContext.Provider value={context}>
440438
{renderContent()}
441439
</NestedOptionContext.Provider>
@@ -446,7 +444,7 @@ const ComponentBase = forwardRef<ComponentBaseRef, any>(
446444
</NestedOptionContext.Provider>
447445
}
448446
</div>
449-
</TemplateDiscoveryContext.Provider>
447+
</TemplateRenderingContext.Provider>
450448
</RestoreTreeContext.Provider>
451449
);
452450
},

packages/devextreme-react/src/core/configuration/config-node.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from 'react';
22
import { separateProps } from '../widget-config';
33
import { IOptionElement } from './react/element';
44
import { getAnonymousTemplate } from './react/templates';
5-
import { TemplateDiscoveryContext } from '../contexts';
5+
import { TemplateRenderingContext } from '../contexts';
66

77
interface IConfigNode {
88
parentNode?: IConfigNode | undefined;
@@ -126,10 +126,10 @@ const createConfigBuilder: (
126126
return template.type === 'children' ? {
127127
...template,
128128
content: React.createElement(
129-
TemplateDiscoveryContext.Provider,
129+
TemplateRenderingContext.Provider,
130130
{
131131
value: {
132-
discoveryRendering: true,
132+
isTemplateRendering: true,
133133
},
134134
},
135135
template.content,

0 commit comments

Comments
 (0)