React prop gets overwritten when using highcharts?

Viewed 175

I have a react component that receives two inputs: data and normalizer.

<ChartInterface
      normalizer={[10, 10, 10, 10]}
      data={[
        { name: "lineA", data: [2, 1, 1, 2] },
        { name: "lineB", data: [1, 0, 3, 0] }
      ]}
    />

The ChartInterface component should render a checkbox and a chart; when the checkbox is not checked, it should render just a chart with the data, which works fine; if the checkbox is checked, the component should divide all the data arrays by the normalizerarray, which i have written functions to do so.

However, the problem is when I repeatedly check and uncheck this checkbox, the data keeps being divided by the array over and over again.

This problem only occurs when I add the <HighchartsReact options={chartOptions} highcharts={Highcharts} /> Component, if i comment it out and just print the chartOptions.lineData the checkbox works as intended. (checking the checkbox shows normalized data, and unchecking shows the original data as it was given as a prop)

The ChartInterface component:

const ChartInterface = (props) => {
  // state for checkbox if chart needs to be normalized or not
  const [isNormalized, setIsNormalized] = useState(false);

  //divides one array by another, element-wise
  function normalize(a, b) {
    return a.map(function (x, idx) {
      return x / b[idx];
    });
  }

  // get data for lines
  let lineData = props.data;

  //if normalized is checked, normalize the data
  if (isNormalized) {
    lineData = lineData.map((line) => {
      return {
        name: line.name,
        data: normalize(line.data, props.normalizer)
      };
    });
  }

  // Chartoptions for highcharts, here we add the line data
  const chartOptions = {
    chart: { type: "area" },
    xAxis: {
      type: "category",
      crosshair: true
    },
    plotOptions: {
      area: {
        stacking: "normal",
        lineWidth: 1
      }
    },
    series: lineData
  };

  return (
    <>
      <input
        type="checkbox"
        checked={isNormalized}
        onChange={(e) => setIsNormalized(e.target.checked)}
      />
      Normalise the data ?
      {<HighchartsReact options={chartOptions} highcharts={Highcharts} />}
      {JSON.stringify(lineData)}
    </>
  );
};

I have also made a code sandbox example where you can easily see by checking and unchecking the checkbox what the problem is: codesandbox demo of problem Thank you for your help

3 Answers

See this updated codesandbox link. For some reason Highcharts is mutating your lineData. There may be a better way, but by using lodash's cloneDeep you can see that it works when you do a deep copy of the data before passing to the Highcharts options.

So your actual problem lies in passing down reference of props.data to chartOptions.series on first render (when checkbox is disabled):

let lineData = props.data

Here you copiing the reference of props.data, then you don't change it because your If is ingored and then you creating chartOptions, where series = props.data. So that's when you referecing Highchars series to your props.data - when you change one - you change the other.

Now good questions is why later on on re-rendering that reference is not being overwritten by new object, but Highchart is mutating initial object instead. And turns out it is Highchart component optimization, looking closely at their code (https://github.com/highcharts/highcharts-react/blob/master/src/HighchartsReact.js) you see they are not re-creating the component, instead they are doing chartRef.current.update which in it's turn does not simply says options = newOptions, but goes property by property and mutates the initial object, and if look above your very initial object is actually your own props.data

To fix the problem you need to do deep copy of props.data, instead of just lineData = props.data, as suggested by Charlies in previous answer

Related