Skip to content

Commit 809fc46

Browse files
authored
Pass unknown (non-ECharts) props through to the div element (#602)
* Pass unknown props through to the div element This fits common React conventions and allows using attributes such as `data-testid` for testing purposes. Related changes: - Adding HTMLAttributes<HTMLDivElement> to EChartsReactProps meant that EChartsReactProps was no longer usable as a `Record<string, unknown>`, which caused problems for the `pick` function. (See https://stackoverflow.com/q/65799316/25507.). I updated the pick function to use a generic type instead. - Add [ignoreRestSiblings](https://eslint.org/docs/latest/rules/no-unused-vars#ignorerestsiblings) so ESLint is happy with the new code. - Fix an unrelated Prettier warning in `componentDidUpdate`. There's a slight risk of backwards-incompatible changes in this release: IF the user previously passed unknown props (e.g., due to typos or mistaken props spreads), they were ignored, but they're now passed through to the div element. Fixes #546 * Update tests * Code review
1 parent a6945e8 commit 809fc46

File tree

7 files changed

+148
-24
lines changed

7 files changed

+148
-24
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"no-unused-vars": 0, // @typescript-eslint/no-unused-vars
1919
"no-inner-declarations": 0,
2020
"prettier/prettier": 2,
21-
"@typescript-eslint/no-unused-vars": 1,
21+
"@typescript-eslint/no-unused-vars": [1, { "ignoreRestSiblings": true }],
2222
"@typescript-eslint/no-non-null-assertion": 0,
2323
"@typescript-eslint/no-explicit-any": 0,
2424
"@typescript-eslint/no-use-before-define": [2, { "functions": false }],

__tests__/charts/simple-spec.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,29 @@ describe('chart', () => {
191191
});
192192
});
193193
});
194+
195+
describe('props', () => {
196+
it('default', () => {
197+
let instance;
198+
const div = createDiv();
199+
div.style.display = 'block';
200+
div.style.width = '1200px';
201+
div.style.height = '720px';
202+
203+
const opts = {
204+
width: null,
205+
height: null,
206+
};
207+
208+
const Comp = <ReactECharts ref={(e) => (instance = e)} option={options} opts={opts} data-testid="props-test" />;
209+
render(Comp, div);
210+
211+
expect(div.querySelector('[data-testid="props-test"]')).toBe(instance.ele);
212+
213+
destroy(div);
214+
expect(div.querySelector('*')).toBe(null);
215+
216+
removeDom(div);
217+
});
218+
});
194219
});

__tests__/helper/pick-spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { pick } from '../../src/helper/pick';
22

33
describe('pick', () => {
44
it('pick', () => {
5-
expect(pick({ a: 1 }, [])).toEqual({});
6-
expect(pick({ a: 1 }, ['b'])).toEqual({});
7-
expect(pick({ a: 1 }, ['a'])).toEqual({ a: 1 });
8-
expect(pick({ a: 1 }, ['a', 'b'])).toEqual({ a: 1 });
5+
const obj: { a: number; b?: number } = { a: 1 };
6+
expect(pick(obj, [])).toEqual({});
7+
expect(pick(obj, ['b'])).toEqual({});
8+
expect(pick(obj, ['a'])).toEqual({ a: 1 });
9+
expect(pick(obj, ['a', 'b'])).toEqual({ a: 1 });
910
});
1011
});

docs/examples/html-props.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
title: HTML Properties
3+
order: 10
4+
---
5+
6+
## HTML Properties
7+
8+
Unknown (non-ECharts) props are passed through to the div element.
9+
10+
```tsx
11+
import React from 'react';
12+
import ReactECharts from 'echarts-for-react';
13+
14+
const Page: React.FC = () => {
15+
const option = {
16+
title: {
17+
text: '堆叠区域图'
18+
},
19+
tooltip : {
20+
trigger: 'axis'
21+
},
22+
legend: {
23+
data:['邮件营销','联盟广告','视频广告']
24+
},
25+
toolbox: {
26+
feature: {
27+
saveAsImage: {}
28+
}
29+
},
30+
grid: {
31+
left: '3%',
32+
right: '4%',
33+
bottom: '3%',
34+
containLabel: true
35+
},
36+
xAxis : [
37+
{
38+
type : 'category',
39+
boundaryGap : false,
40+
data : ['周一','周二','周三','周四','周五','周六','周日']
41+
}
42+
],
43+
yAxis : [
44+
{
45+
type : 'value'
46+
}
47+
],
48+
series : [
49+
{
50+
name:'邮件营销',
51+
type:'line',
52+
stack: '总量',
53+
areaStyle: {normal: {}},
54+
data:[120, 132, 101, 134, 90, 230, 210]
55+
},
56+
{
57+
name:'联盟广告',
58+
type:'line',
59+
stack: '总量',
60+
areaStyle: {normal: {}},
61+
data:[220, 182, 191, 234, 290, 330, 310]
62+
},
63+
{
64+
name:'视频广告',
65+
type:'line',
66+
stack: '总量',
67+
areaStyle: {normal: {}},
68+
data:[150, 232, 201, 154, 190, 330, 410]
69+
}
70+
]
71+
};
72+
73+
const handleDemoButton = () => {
74+
console.log(document.querySelector(['[data-testid="html-props-demo"]']));
75+
window.alert('Open console, see the log detail.')
76+
};
77+
78+
return (
79+
<>
80+
<ReactECharts
81+
option={option}
82+
style={{ height: 400 }}
83+
role="figure"
84+
data-testid="html-props-demo"
85+
/>
86+
<button type="button" onClick={handleDemoButton}>Demo</button>
87+
</>
88+
);
89+
};
90+
91+
export default Page;
92+
```

src/core.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,7 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
5252
// 以下属性修改的时候,需要 dispose 之后再新建
5353
// 1. 切换 theme 的时候
5454
// 2. 修改 opts 的时候
55-
if (
56-
!isEqual(prevProps.theme, this.props.theme) ||
57-
!isEqual(prevProps.opts, this.props.opts)
58-
) {
55+
if (!isEqual(prevProps.theme, this.props.theme) || !isEqual(prevProps.opts, this.props.opts)) {
5956
this.dispose();
6057

6158
this.renderNewEcharts(); // 重建
@@ -70,8 +67,8 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
7067
}
7168

7269
// when these props are not isEqual, update echarts
73-
const pickKeys = ['option', 'notMerge', 'replaceMerge', 'lazyUpdate', 'showLoading', 'loadingOption'];
74-
if (!isEqual(pick(this.props, pickKeys), pick(prevProps, pickKeys))) {
70+
const pickKeys = ['option', 'notMerge', 'replaceMerge', 'lazyUpdate', 'showLoading', 'loadingOption'] as const;
71+
if (!isEqual(pick(this.props, pickKeys), pick(prevProps as any, pickKeys))) {
7572
this.updateEChartsOption();
7673
}
7774

@@ -243,7 +240,24 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
243240
}
244241

245242
render(): JSX.Element {
246-
const { style, className = '' } = this.props;
243+
const {
244+
style,
245+
className = '',
246+
echarts,
247+
option,
248+
theme,
249+
notMerge,
250+
replaceMerge,
251+
lazyUpdate,
252+
showLoading,
253+
loadingOption,
254+
opts,
255+
onChartReady,
256+
onEvents,
257+
shouldSetOption,
258+
autoResize,
259+
...divHTMLAttributes
260+
} = this.props;
247261
// default height = 300
248262
const newStyle = { height: 300, ...style };
249263

@@ -254,6 +268,7 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
254268
}}
255269
style={newStyle}
256270
className={`echarts-for-react ${className}`}
271+
{...divHTMLAttributes}
257272
/>
258273
);
259274
}

src/helper/pick.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* @param obj
44
* @param keys
55
*/
6-
export function pick(obj: Record<string, unknown>, keys: string[]): Record<string, unknown> {
7-
const r = {};
6+
export function pick<T extends object>(obj: T, keys: readonly (keyof T)[]): Partial<T> {
7+
const r = {} as Partial<T>;
88
keys.forEach((key) => {
99
r[key] = obj[key];
1010
});

src/types.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { CSSProperties } from 'react';
21
import type { EChartsType } from 'echarts';
32

43
/**
@@ -16,19 +15,11 @@ export type Opts = {
1615
readonly locale?: string;
1716
};
1817

19-
export type EChartsReactProps = {
18+
export type EChartsReactProps = React.HTMLAttributes<HTMLDivElement> & {
2019
/**
2120
* echarts library entry, use it for import necessary.
2221
*/
2322
readonly echarts?: any;
24-
/**
25-
* `className` for container
26-
*/
27-
readonly className?: string;
28-
/**
29-
* `style` for container
30-
*/
31-
readonly style?: CSSProperties;
3223
/**
3324
* echarts option
3425
*/

0 commit comments

Comments
 (0)