Skip to content

Commit 2346edc

Browse files
gggritsohustcc
andauthored
Clean up event listeners from previous renders (#618)
* Add second test series This makes it possible to dispatch legend events for testing. * Add test case This verifies the bug by adding a failing test case. On re-render there are two event listeners attached to the chart instance, there should be only one! * Correctly unbind the current event handlers Keep track of the handlers added to the current instance. Unbind them before binding new events. * Rename onEvents to eventHandlerRefs for clarity * Bump version from 3.0.5 to 3.0.6 --------- Co-authored-by: hustcc <i@hust.cc>
1 parent 6bbc4c5 commit 2346edc

File tree

3 files changed

+90
-14
lines changed

3 files changed

+90
-14
lines changed

__tests__/charts/simple-spec.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type { EChartsOption } from '../../src/';
44
import { render, destroy, createDiv, removeDom } from '../utils';
55

66
const options: EChartsOption = {
7+
legend: {
8+
data: ['series1', 'series2'],
9+
},
710
xAxis: {
811
type: 'category',
912
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
@@ -13,10 +16,17 @@ const options: EChartsOption = {
1316
},
1417
series: [
1518
{
19+
name: 'series1',
1620
data: [820, 932, 901, 934, 1290, 1330, 1320],
1721
type: 'line',
1822
smooth: true,
1923
},
24+
{
25+
name: 'series2',
26+
data: [720, 832, 801, 834, 1190, 1230, 1220],
27+
type: 'line',
28+
smooth: true,
29+
},
2030
],
2131
};
2232

@@ -216,4 +226,57 @@ describe('chart', () => {
216226
removeDom(div);
217227
});
218228
});
229+
230+
it('updates event handler on re-render', (done) => {
231+
let instance;
232+
const div = createDiv();
233+
234+
const handlerA = jest.fn();
235+
const handlerB = jest.fn();
236+
237+
// First render, with first legend handler
238+
const Comp1 = (
239+
<ReactECharts
240+
ref={(e) => (instance = e)}
241+
option={options}
242+
onEvents={{
243+
legendselectchanged: handlerA,
244+
}}
245+
onChartReady={(chart) => {
246+
// @ts-ignore - accessing internal property for testing
247+
const handlers = chart._$handlers?.legendselectchanged;
248+
249+
expect(handlers).toBeDefined();
250+
expect(handlers.length).toBe(1);
251+
252+
// Re-render with second legend handler
253+
const Comp2 = (
254+
<ReactECharts
255+
ref={(e) => (instance = e)}
256+
option={options}
257+
onEvents={{
258+
legendselectchanged: handlerB,
259+
}}
260+
/>
261+
);
262+
render(Comp2, div);
263+
264+
// Wait a tick for re-render to complete
265+
setTimeout(() => {
266+
const updatedChart = instance.getEchartsInstance();
267+
// @ts-ignore - accessing internal property for testing
268+
const handlersAfterReRender = updatedChart._$handlers?.legendselectchanged;
269+
expect(handlersAfterReRender).toBeDefined();
270+
expect(handlersAfterReRender.length).toBe(1);
271+
272+
destroy(div);
273+
removeDom(div);
274+
done();
275+
}, 0);
276+
}}
277+
/>
278+
);
279+
280+
render(Comp1, div);
281+
});
219282
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "echarts-for-react",
3-
"version": "3.0.5",
3+
"version": "3.0.6",
44
"description": " Apache Echarts components for React.",
55
"main": "lib/index.js",
66
"module": "esm/index.js",

src/core.tsx

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,18 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
2626
*/
2727
protected echarts: any;
2828

29+
/**
30+
* Currently attached ECharts event listeners
31+
*/
32+
private eventHandlerRefs: Record<string, Function>;
33+
2934
constructor(props: EChartsReactProps) {
3035
super(props);
3136

3237
this.echarts = props.echarts;
3338
this.ele = null;
3439
this.isInitialResize = true;
40+
this.eventHandlerRefs = {};
3541
}
3642

3743
componentDidMount() {
@@ -62,7 +68,7 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
6268
// 修改 onEvent 的时候先移除历史事件再添加
6369
const echartsInstance = this.getEchartsInstance();
6470
if (!isEqual(prevProps.onEvents, this.props.onEvents)) {
65-
this.offEvents(echartsInstance, prevProps.onEvents);
71+
this.unbindEvents(echartsInstance);
6672
this.bindEvents(echartsInstance, this.props.onEvents);
6773
}
6874

@@ -163,15 +169,21 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
163169

164170
// bind the events
165171
private bindEvents(instance, events: EChartsReactProps['onEvents']) {
166-
function _bindEvent(eventName: string, func: Function) {
172+
const _bindEvent = (eventName: string, func: Function) => {
167173
// ignore the event config which not satisfy
168174
if (isString(eventName) && isFunction(func)) {
169175
// binding event
170-
instance.on(eventName, (param) => {
176+
const handler = (param) => {
171177
func(param, instance);
172-
});
178+
};
179+
180+
instance.on(eventName, handler);
181+
182+
// Store currently bound event handlers. This way we can unbind them
183+
// on next component update, before binding the new handlers.
184+
this.eventHandlerRefs[eventName] = handler;
173185
}
174-
}
186+
};
175187

176188
// loop and bind
177189
for (const eventName in events) {
@@ -181,15 +193,16 @@ export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
181193
}
182194
}
183195

184-
// off the events
185-
private offEvents(instance, events: EChartsReactProps['onEvents']) {
186-
if (!events) return;
187-
// loop and off
188-
for (const eventName in events) {
189-
if (isString(eventName)) {
190-
instance.off(eventName, events[eventName]);
191-
}
196+
/**
197+
* Unbind all currently bound event handlers. Importantly, this does not
198+
* unbind the `"finished"` event that is used for chart initialization.
199+
*/
200+
private unbindEvents(instance: EChartsInstance) {
201+
for (const [eventName, listener] of Object.entries(this.eventHandlerRefs)) {
202+
instance.off(eventName, listener);
192203
}
204+
205+
this.eventHandlerRefs = {};
193206
}
194207

195208
/**

0 commit comments

Comments
 (0)