.detach() not removing the window resize listener.
I have this react component
class ChartCell extends React.Component{
constructor(props, context) {
super(props, context);
this.chartCreated = this.chartCreated.bind(this);
}
componentDidMount(){
let mergedOptions = this.props.chartOptions;
console.log('mounted');
mergedOptions = _.assign(mergedOptions, defaultOptions);
this.chartObj = new Chartist.Line(this.refs.chart, this.props.chartData, mergedOptions);
this.chartObj.on('created', this.chartCreated); // Tried to call detach on "created" doesn't work
this.chartObj.on('initial', this.chartCreated); // Neither does calling on "initial"
}
chartCreated(){
console.log('detached');
this.chartObj.detach(); //doesn't remove the event listener
window.removeEventListener('resize', this.chartObj.resizeListener); //successfully removes the listener
}
render(){
return (<td><div ref="chart" className="chart-div"/></td>)
}
}
It does not clear the resize event listener. Which causes my page to lock up while redrawing every chart on a resize. I'm using a static sized div to contain the charts so I don't need to resize the charts on window redraw.
Investigation: So base.detach() will only not remove the listener if timeout is not set:
function detach() {
// Only detach if initialization already occurred on this chart. If this chart still hasn't initialized (therefore
// the initializationTimeoutId is still a valid timeout reference, we will clear the timeout
if(!this.initializeTimeoutId) {
window.removeEventListener('resize', this.resizeListener);
this.optionsProvider.removeMediaQueryListeners();
} else {
window.clearTimeout(this.initializeTimeoutId);
}
return this;
}
My guess here is base.initialize is the problem:
function initialize() {
// Add window resize listener that re-creates the chart
window.addEventListener('resize', this.resizeListener);
// Obtain current options based on matching media queries (if responsive options are given)
// This will also register a listener that is re-creating the chart based on media changes
this.optionsProvider = Chartist.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter);
// Register options change listener that will trigger a chart update
this.eventEmitter.addEventHandler('optionsChanged', function() {
this.update();
}.bind(this));
// Before the first chart creation we need to register us with all plugins that are configured
// Initialize all relevant plugins with our chart object and the plugin options specified in the config
if(this.options.plugins) {
this.options.plugins.forEach(function(plugin) {
if(plugin instanceof Array) {
plugin[0](this, plugin[1]);
} else {
plugin(this);
}
}.bind(this));
}
// Event for data transformation that allows to manipulate the data before it gets rendered in the charts
this.eventEmitter.emit('data', {
type: 'initial',
data: this.data
});
// Create the first chart
this.createChart(this.optionsProvider.getCurrentOptions());
// As chart is initialized from the event loop now we can reset our timeout reference
// This is important if the chart gets initialized on the same element twice
this.initializeTimeoutId = undefined;
}
this.initializeTimeoutId = undefined; is cleared AFTER the 'initial' event is emitted. That clearly shows why trying to detach() after 'initial' doesn't work. As for it not working on 'created" I don't know the lifecycle but maybe there's a race condition where initial fires, which causes created to fire later but before the initialize() function finishes and clears the timeout? So my question is A: should initializeTimeoutId be cleared before emitting the 'initial' event. OR B: which event should I wait for before detaching?
Running into the exact same issue here. Did you ever find a workaround?
I think I did, but I don’t remember and the code is with my previous employer sorry :(
No problem, I found a solution. It's not pretty, but for anybody else that runs into this issue this may help:
const chart = new Chartist.Bar(chartEl, options)
// Remove chartist resize event
setTimeout(() => {
window.removeEventListener('resize', chart.resizeListener)
}, 100)
// Add our own resize event with iOS fix thanks to https://stackoverflow.com/questions/8898412/iphone-ipad-triggering-unexpected-resize-events
let windowWidth = window.innerWidth
window.addEventListener('resize', () => {
// Check window width has actually changed and it's not just iOS triggering a resize event on scroll
if (window.innerWidth != windowWidth) {
// Update the window width for next time
windowWidth = window.innerWidth
chart.update()
}
})
I am not sure why but I had to set a very short timeout to remove the chartist resize event. Makes me sad how hacky it feels but what are you gonna do 😢
Is this still an issue years later?
Do we need to manually manage window resize, in relation to iOS (for instance), with Chartist in 2024?