Skip to content

Commit f893322

Browse files
fix(react-wrapper): fixed T1279646 (#29268) (#29339)
Co-authored-by: sergey-kras <[email protected]>
1 parent d0acdba commit f893322

File tree

5 files changed

+123
-35
lines changed

5 files changed

+123
-35
lines changed

apps/react/examples/Examples.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import TextBoxExample from './text-box-example';
2323
import ToolbarExample from './toolbar-example';
2424
import ValidationExample from './validation-example';
2525
// import DateBoxExample from './date-box-example';
26+
import SelectBoxCustomExample from './selector-custom-example';
2627

2728
const Examples = () => {
2829
return (
@@ -85,7 +86,11 @@ const Examples = () => {
8586
<SelectBoxExample />
8687
</Example>
8788

88-
{/* <StandaloneValidatorExample /> */}
89+
<Example title="SelectBox example with custom components">
90+
<SelectBoxCustomExample />
91+
</Example>
92+
93+
{/*<StandaloneValidatorExample />*/}
8994

9095
</div>
9196
);

apps/react/examples/example-block.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ interface IProps {
77
}
88

99
const Example = (props: IProps): JSX.Element => {
10-
const { state, title, children } = props;
10+
const { state, title, children, id } = props;
1111
let stateBlock: JSX.Element | null = null;
1212
if (state) {
1313
stateBlock = <pre className="example-state">{JSON.stringify(state, null, ' ')}</pre>;
1414
}
1515

1616
return (
17-
<div className="example-block">
17+
<div className="example-block" id={id}>
1818
<div className="example-header">
1919
<h4 className="bg-primary example-title">{title || 'example'}</h4>
2020
{stateBlock}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React, { useCallback, useState } from "react";
2+
import { SelectBoxTypes } from "devextreme-react/select-box";
3+
import { SelectBox } from "devextreme-react";
4+
import { TextBox } from "devextreme-react";
5+
6+
const products = [{
7+
ID: 1,
8+
Name: 'HD Video Player',
9+
Price: 330,
10+
Current_Inventory: 225,
11+
Backorder: 0,
12+
Manufacturing: 10,
13+
Category: 'Video Players',
14+
ImageSrc: 'https://js.devexpress.com/React/Demos/WidgetsGallery/JSDemos/images/products/1-small.png',
15+
}, {
16+
ID: 2,
17+
Name: 'SuperHD Player',
18+
Price: 400,
19+
Current_Inventory: 150,
20+
Backorder: 0,
21+
Manufacturing: 25,
22+
Category: 'Video Players',
23+
ImageSrc: 'https://js.devexpress.com/React/Demos/WidgetsGallery/JSDemos/images/products/2-small.png',
24+
}];
25+
26+
const templatedProductLabel = { "aria-label": "Templated Product" };
27+
const nameLabel = { "aria-label": "Name" };
28+
function Item(data) {
29+
return (
30+
<div className="custom-item">
31+
<img
32+
alt="Product name"
33+
src={data.ImageSrc}
34+
/>
35+
<div className="product-name">{data.Name}</div>
36+
</div>
37+
);
38+
}
39+
40+
function Field(data: { ImageSrc: any; Name: any }) {
41+
return (
42+
<div className="custom-item">
43+
<div className="value">{data.Name}</div>
44+
<img
45+
alt="Product name"
46+
src={data && data.ImageSrc} />
47+
<TextBox
48+
value= {data?.Name || ''}
49+
className="product-name"
50+
inputAttr={nameLabel}
51+
defaultValue={data && data.Name}
52+
readOnly={true}
53+
/>
54+
</div>
55+
);
56+
}
57+
58+
function SelectBoxCustomExample() {
59+
const [value, setValue] = useState('1');
60+
const onValueChanged = useCallback((e: SelectBoxTypes.ValueChangedEvent) => {
61+
setValue(e.value)
62+
}, []);
63+
64+
return (
65+
<div>
66+
<div>
67+
value: {value}
68+
</div>
69+
<SelectBox
70+
onValueChanged={onValueChanged}
71+
id="custom-templates"
72+
dataSource={products}
73+
displayExpr="Name"
74+
inputAttr={templatedProductLabel}
75+
valueExpr="ID"
76+
defaultValue={products[0].ID}
77+
fieldRender={Field}
78+
itemRender={Item}
79+
/>
80+
</div>
81+
);
82+
}
83+
84+
export default SelectBoxCustomExample;

packages/devextreme-react/src/core/template-manager.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ function normalizeProps(props: ITemplateArgs): ITemplateArgs | ITemplateArgs['da
3636
return props;
3737
}
3838

39+
const createMapKey = (key1: any, key2: HTMLElement) => ({ key1, key2 });
40+
41+
const unsubscribeOnRemoval = (container: HTMLElement, onRemoved: () => void) => {
42+
if (container.nodeType === Node.ELEMENT_NODE) {
43+
events.off(container, DX_REMOVE_EVENT, onRemoved);
44+
}
45+
};
46+
47+
const subscribeOnRemoval = (container: HTMLElement, onRemoved: () => void) => {
48+
if (container.nodeType === Node.ELEMENT_NODE) {
49+
events.on(container, DX_REMOVE_EVENT, onRemoved);
50+
}
51+
};
52+
53+
const unwrapElement = (element: any): HTMLElement => (element.get ? element.get(0) : element);
54+
55+
const getRandomId = () => `${generateID()}${generateID()}${generateID()}`;
56+
3957
export const TemplateManager: FC<TemplateManagerProps> = ({ init, onTemplatesRendered }) => {
4058
const [instantiationModels, setInstantiationModels] = useState({
4159
collection: new TemplateInstantiationModels(),
@@ -44,24 +62,6 @@ export const TemplateManager: FC<TemplateManagerProps> = ({ init, onTemplatesRen
4462
const widgetId = useRef('');
4563
const templateFactories = useRef<Record<string, TemplateFunc>>({});
4664

47-
const subscribeOnRemoval = useCallback((container: HTMLElement, onRemoved: () => void) => {
48-
if (container.nodeType === Node.ELEMENT_NODE) {
49-
events.on(container, DX_REMOVE_EVENT, onRemoved);
50-
}
51-
}, []);
52-
53-
const unsubscribeOnRemoval = useCallback((container: HTMLElement, onRemoved: () => void) => {
54-
if (container.nodeType === Node.ELEMENT_NODE) {
55-
events.off(container, DX_REMOVE_EVENT, onRemoved);
56-
}
57-
}, []);
58-
59-
const unwrapElement = useCallback((element: any): HTMLElement => (element.get ? element.get(0) : element), []);
60-
61-
const createMapKey = useCallback((key1: any, key2: HTMLElement) => ({ key1, key2 }), []);
62-
63-
const getRandomId = useCallback(() => `${generateID()}${generateID()}${generateID()}`, []);
64-
6565
const { collection } = instantiationModels;
6666

6767
const getRenderFunc: GetRenderFuncFn = useCallback((templateKey) => ({
@@ -99,7 +99,7 @@ export const TemplateManager: FC<TemplateManagerProps> = ({ init, onTemplatesRen
9999
setInstantiationModels({ collection });
100100

101101
return containerElement;
102-
}, [unsubscribeOnRemoval, createMapKey, collection]);
102+
}, [collection]);
103103

104104
useMemo(() => {
105105
function getTemplateFunction(template: ITemplate): TemplateFunc {

packages/devextreme-react/src/core/template-wrapper.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
useMemo,
1111
memo,
1212
FC,
13+
MutableRefObject,
1314
} from 'react';
1415

1516
import { createPortal } from 'react-dom';
@@ -25,9 +26,9 @@ const createHiddenNode = (
2526
const style = { display: 'none' };
2627
switch (containerNodeName) {
2728
case 'TABLE':
28-
return <tbody style={style} ref={ref}></tbody>;
29+
return <tbody style={style} ref={ref} />;
2930
case 'TBODY':
30-
return <tr style={style} ref={ref}></tr>;
31+
return <tr style={style} ref={ref} />;
3132
default:
3233
return React.createElement(defaultElement, { style, ref });
3334
}
@@ -82,17 +83,15 @@ const TemplateWrapperComponent: FC<TemplateWrapperProps> = ({
8283
}
8384

8485
return () => {
85-
if (element.current) {
86-
container.appendChild(element.current);
87-
}
88-
89-
if (hiddenNodeElement.current) {
90-
container.appendChild(hiddenNodeElement.current);
91-
}
92-
93-
if (removalListenerElement.current) {
94-
container.appendChild(removalListenerElement.current);
95-
}
86+
const safeAppend = (child?: MutableRefObject<HTMLElement | undefined>) => {
87+
if (child?.current && container && !container.contains(child.current)) {
88+
container.appendChild(child.current);
89+
}
90+
};
91+
92+
safeAppend(element);
93+
safeAppend(hiddenNodeElement);
94+
safeAppend(removalListenerElement);
9695

9796
if (el) {
9897
events.off(el, DX_REMOVE_EVENT, onTemplateRemoved);

0 commit comments

Comments
 (0)