I am fairly new to React and Javascript space. I am trying to build a component which uses the HighCharts library and one of the things I am implementing is a formatter callback. This callback needs to access the props passed to this component from the parent component. The property I am using is called metricUnit
. The behavior I am noticing right now is that even though the metricUnit
updates in the component, it doesn't relay down to this callback function. What am I doing incorrectly here?
const BoxChart = ({ panelId, group, metric, metricUnit, min, max,
data}) => {
console.log(panelId, metric, metricUnit) // this shows the updated value
const pointFormatterCallback = useCallback(function() {
return function() {
console.log(metric);
var value = this.y;
if (value % 1) {
value = Highcharts.numberFormat(value, 2);
} else {
value = Highcharts.numberFormat(value, 0);
}
const unitStr = metricUnit ? " (" + metricUnit + ")" : ""; //this still shows the older value
return this.name + ': ' + value + unitStr + '<br/>';
}
}, [metric, metricUnit]);
const [defaultChartOptions, setDefaultChartOptions] = useState({
chart: {
style: {
fontFamily: '"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;',
width: '100%'
},
type: 'boxplot',
zoomType: 'x'
},
xAxis: {
categories: [],
title: {
text: group,
style: {
fontWeight: 'bold'
}
}
},
yAxis: {
title: {
text: metric,
style: {
fontWeight: 'bold'
}
},
labels: {
formatter: function() {
var outputVal = this.value;
if(outputVal % 1) {
outputVal = Highcharts.numberFormat(this.value, 2);
} else {
outputVal = Highcharts.numberFormat(this.value, 0);
}
return outputVal;
}
},
startOnTick: false,
endOnTick: false
},
title: {
text: null
},
credits: {
enabled: false
},
legend: {
maxHeight:100,
margin: 6
},
plotOptions: {
boxplot: {
tooltip: {
valueDecimals: 2
}
},
scatter: {
marker: {
radius: 5
},
tooltip: {
headerFormat: "<b>{series.name}</b><br>",
pointFormatter: pointFormatterCallback(),
valueDecimals: 2
}
}
}
});
const chartWrapper = useRef(null);
const { width } = useElementSize(chartWrapper)
const chartComponent = useRef(null);
useEffect(() => {
console.log("Current width", width);
if(chartComponent.current) {
const chart = chartComponent.current.chart;
chart.update({
chart: {
width: width
}
});
}
}, [width]);
if(data === undefined || Object.keys(data).length === 0 || data.categories.length === 0) {
return(<div style={{position: 'absolute', top: '50%', left: '50%', transform: 'translateX(-50%) translateY(-50%)'}}>No data available</div>);
} else {
const series = data['series'];
const categories = data['categories'];
const chartOptions = {...defaultChartOptions};
const unitStr = metricUnit ? " (" + metricUnit + ")" : "";
chartOptions['yAxis']['title']['text'] = metric + unitStr;
chartOptions['xAxis']['title']['text'] = group;
chartOptions['series'] = series;
chartOptions['xAxis']['categories'] = categories;
if(min !== null && !isNaN(min)) {
chartOptions['yAxis']['min'] = min;
}
if(max !== null && !isNaN(max)) {
chartOptions['yAxis']['max'] = max;
}
return (
<div ref={chartWrapper} style={{overflow: "hidden", width: "100%"}}>
<HighchartsReact highcharts={Highcharts} options={chartOptions}
ref={chartComponent}
/>
</div>
);
}
}
Here is a sample reproduction: https://codesandbox.io/s/determined-tesla-26z00?file=/demo.jsx
Right now your callback passed into useCallback
returns a function which does the work. There will be some unexpected closure behavior because of this. My best attempt at explaining it is thus:
When you set pointFormatterCallback
to pointFormatter
you then invoke it, and the value of metricUnit in the function returned will be whatever it is set to at invocation time . In order to read metric when it is updated, you'd have to re-invoke your pointFormatterCallback
in order to access that new updated metric value.
Try removing the returned function like so:
const pointFormatterCallback = useCallback(function() {
console.log(metric);
var value = this.y;
if (value % 1) {
value = Highcharts.numberFormat(value, 2);
} else {
value = Highcharts.numberFormat(value, 0);
}
const unitStr = metricUnit ? " (" + metricUnit + ")" : ""; //this still shows the older value
return this.name + ': ' + value + unitStr + '<br/>';
}, [metric, metricUnit]);
Notice that changing parent state doesn't re-render the tooltipCallback
function and it still has references to the previous unit.
Demo: https://codesandbox.io/s/patient-fog-mxdjr?file=/demo.jsx:708-723 - check the console to see what I am talking about.
I think that you should use the useEffect
hook to set this parameter if has been changed.
You can even make it easier by just update the tooltip formatter - it makes y9ou sure that the chart component will be re-rendered.
Demo: https://stackblitz.com/edit/react-ruwaxu?file=index.js
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.