Skip to content

Commit 1edac4a

Browse files
committed
Remove cancelable events from updateEvents to avoid two listeners issue
1 parent dc508ad commit 1edac4a

2 files changed

Lines changed: 28 additions & 17 deletions

File tree

src/__tests__/react-plotly.test.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,20 +219,30 @@ describe('<Plotly/>', () => {
219219
});
220220
});
221221

222-
// The drill-down traces all share plotly.js' sunburst click handler,
223-
// which mutates `trace.level` — so onUpdate has to fire for each of them
224-
// or consumers never see the new level. See issue #375.
222+
// Cancelable — the consumer's handler must be the only listener or
223+
// plotly could drop its `return false`. See the note in `events.js`.
225224
test.each(['SunburstClick', 'TreemapClick', 'IcicleClick'])(
226-
'fires onUpdate for %s drill-downs',
225+
'attaches only the consumer handler to %s',
227226
(eventName) => {
228-
const onUpdate = jest.fn();
227+
const handler = () => false;
229228

230-
return createPlot({onUpdate}).then((plot) => {
231-
plot.gd.emit(getPlotlyEventName(eventName));
232-
expect(onUpdate).toHaveBeenCalled();
233-
});
229+
return createPlot({[getPropName(eventName)]: handler, onUpdate: () => {}}).then(
230+
(plot) => {
231+
const listeners = [].concat(plot.gd.__ee__[getPlotlyEventName(eventName)] || []);
232+
expect(listeners).toEqual([handler]);
233+
}
234+
);
234235
}
235236
);
237+
238+
test('fires onUpdate when a drill-down animation completes', () => {
239+
const onUpdate = jest.fn();
240+
241+
return createPlot({onUpdate}).then((plot) => {
242+
plot.gd.emit('plotly_animated');
243+
expect(onUpdate).toHaveBeenCalled();
244+
});
245+
});
236246
});
237247

238248
describe('StrictMode', () => {

src/events.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
// - events are attached as `'plotly_' + name.toLowerCase()`
55
// - react props are `'on' + name`
66
//
7-
// `triggersUpdate` marks events that plotly.js emits *after* mutating the
8-
// figure itself (drill-downs, zooms, restyles). For those the wrapper has to
9-
// re-read the graph div and fire `onUpdate`. Keeping the flag on the same
10-
// record as the name is deliberate: a new drill-down event cannot be added
11-
// without deciding whether it updates the figure.
7+
// `triggersUpdate` marks events plotly.js emits *after* changing the figure;
8+
// the wrapper listens to those and fires `onUpdate`. Never set it on a
9+
// cancelable event — that adds a second listener alongside the consumer's,
10+
// and plotly keeps only the last listener's return value, so a consumer's
11+
// `return false` could be discarded. The drill-down clicks rely on
12+
// `plotly_animated` instead.
1213
//
1314
// The `on*` prop types in `factory.d.ts` are maintained by hand against this list
1415
export const events = [
@@ -29,7 +30,7 @@ export const events = [
2930
{name: 'Framework'},
3031
{name: 'Hover'},
3132
{name: 'HoverAnywhere'},
32-
{name: 'IcicleClick', triggersUpdate: true},
33+
{name: 'IcicleClick'},
3334
{name: 'LegendClick'},
3435
{name: 'LegendDoubleClick'},
3536
{name: 'Relayout', triggersUpdate: true},
@@ -41,10 +42,10 @@ export const events = [
4142
{name: 'SliderChange'},
4243
{name: 'SliderEnd'},
4344
{name: 'SliderStart'},
44-
{name: 'SunburstClick', triggersUpdate: true},
45+
{name: 'SunburstClick'},
4546
{name: 'Transitioning'},
4647
{name: 'TransitionInterrupted'},
47-
{name: 'TreemapClick', triggersUpdate: true},
48+
{name: 'TreemapClick'},
4849
{name: 'Unhover'},
4950
{name: 'WebGlContextLost'},
5051
];

0 commit comments

Comments
 (0)