Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Simple Pattern to use Highchart and React

Posted on: 2018-06-12

At the time I am compositing this entry, Highchart doesn't collaborate naturally in a React project. There is an open source project available but it is not part of the official release. However, using directly the library from Highchart into your TypeScript and React project is not a huge burden. In this article, we will see how to create a React component that uses Highchart.

The first step is to get the official library and the definition file. The types are not in the main repository and are a major version late. However, it was enough in my experience to work. You need to NPM install "highcharts" and "@types/highcharts".

You will need to create a new TSX file and import one or many packages. The number of packages depends on what you want to do with HighChart. If you need to use the exporting capability, you will need to import an additional module. If you need the offline flavor of exporting, you will need also to import a package. For the completeness of the example, let's get them all.

import Highcharts, { CSSObject, Options, SeriesObject, PlotBands } from "highcharts"; 
import Exporter from "highcharts/modules/exporting"; 
import OE from "highcharts/modules/offline-exporting"; 

The Exporting and OE are functions. They must be applied to the HightCharts function right below you imports.

Exporter(Highcharts); 
OE(Highcharts); 

The next step is to create a React component that will have a componentDidUpdate, a render, and a componentDidMount. Also, the component must have one reference to Highchart and one other reference to the HTML container. The container may be used for adding dynamic animation with CSS and also to indicate where to inject the Highchart component. The Highchart component reference will be used to push data or user's options during the lifetime of the component.

private hightChartContainer: HTMLDivElement | null = null; 
private highChartControl: Highcharts.ChartObject | undefined = undefined; 

The render function creates the container that hosts the Highchart control.

public render(): JSX.Element { 
  const isChartVisible = this.props.source.graphData !== undefined; 
  const chart = <div style={{ display: (isChartVisible ? "block" : "none") }} ref={(div) => { this.hightChartContainer = div; }} />; 
  return <> <div 
    style={{ display: (isChartVisible ? "none" : "flex"), }}> Loading your data 
    </div> {chart} 
  </>; } 

The render will be executed once and then the componentDidMount function is executed once as well. Meanwhile, the componentDidMount function does the heavy lifting by instantiating the Highchart component. In reality, this function is way bigger than what I am providing. It contains all the configuration you can apply to a Highchart.

public componentDidMount() { 
  const that = this; 
  if (this.hightChartContainer !== null) { 
    Highcharts.setOptions(theme); 
    const options: Options = { 
      chart: { 
        type: "line", }, 
      series: [], 
      }; 
    this.highChartControl = Highcharts.chart(this.hightChartContainer, options); 
    if (this.props.source.graphData !== undefined) { 
      this.componentDidUpdate(); 
    } 
  } 
} 

Finally, the componentDidUpdate has the role to push the values to the chart. The componentDidMount sets the series to an empty array because the data might not yet be available. However, the componentDidUpdate will be called every time new properties change which make it a good place to update the chart.

public componentDidUpdate() { 
  if (this.hightChartContainer !== null && this.highChartControl !== undefined && this.props.source.graphData !== undefined) { 
    const data = this.props... // Get your data 
    const series: GraphData[] = []; 
    while (this.highChartControl.series.length > 0) { 
      this.highChartControl.series[0].remove(false); 
    }

    // Values dimension represent each series 
    scenarioUpdate.addMarker("CreateSeries"); 
    rawValues.forEach((value: number[], index: number) => { 
      const time = data.start + index * data.step; 
      for (let iSerie = 0; iSerie < value.length; iSerie++) { 
        if (index === 0) { // Only first time we create series 
          series.push({ seriesName: data.legend[iSerie], seriesValues: [] }); 
        } 
        series[iSerie].seriesValues.push({ time: time, value: value[iSerie] }); 
      } 
    });

    let loopCount = 0; 
    series.forEach((serieData: GraphData, index: number) => { 
      if (this.highChartControl !== undefined) { 
        const values = serieData.seriesValues
          .map((d: GraphDataValue) => { loopCount++; return [d.time, d.value]; }); 
        this.highChartControl.addSeries({}, false, false); 
        this.highChartControl.series[index].setData(values, false, false, false); 
        this.highChartControl.series[index].update({ name: serieData.seriesName }, false); 
      } 
    });

    window.requestAnimationFrame(() => { 
      window.requestAnimationFrame(() => { 
        if (this.highChartControl !== undefined) { 
          this.highChartControl.reflow(); 
          this.highChartControl.redraw(); 
        } 
      }); 
    }); 
  } 
} 

Once again, I removed a lot of the code from which I have been inspired. The goal here is to create all your series but before creating series, we remove all the existing one. In the end, we reflow and redraw which animated the graph. Indeed, a good "shouldComponentUpdate" is required to avoid updating for no reason.

It might look like a lot of code but in reality, it is so close to the Highcharts documentation that it is easy to maintain. The key concept is to have the container that host Highchart build one time and to update the existing one.