Search code examples
reactjscomponentsmount

Unhandled Rejection (TypeError): Cannot read property 'getContext' of null


In my Application there are 3 pages which are login,register and dash board. when I redirect back to dash board when at login I got this error. The error error is in a component which is called Sensor Reading Chart SensorReadingChart.js

class SensorReadingChart extends Component {

    _isMounted = false;
    chartRef = React.createRef();
    constructor(props) {
        super(props);
        threshold = props.threshold
        xAxisLabel = props.xAxisLabel
        yAxisLabel = props.yAxisLabel
        chartTitle = props.chartTitle
        x = props.x
        y = props.y
        location = props.location
        icon = props.icon
        apiEndPoint = props.apiEndPoint
        yUnit = props.yUnit
        themeColor = props.themeColor

        this.state = {
            sensorReadings:[]
        }
    }

    render() {
        dataPoints = this.state.sensorReadings.map(
            (sensor) => {
                return {
                    x: sensor[x], //change this to proper key
                    y: sensor[y], //change this to proper key
                    location: sensor[location], //change this to proper key
                }
            });
        thresholdLine = this.state.sensorReadings.map(
            (sensor) => {
                return {
                    x: sensor.id, //change this to proper key
                    y: threshold,
                }
            });
        options = {
            type: "line",
            data: {
                xLabels: dataPoints.map(x=>{return x.x}),
                datasets: [
                    {
                        data: dataPoints.map(y=>{return y.y}), //Sample data set [{x:1, y:21},{x:2, y:25},{x:3, y:31},{x:4, y:11},]
                        showLine: true,
                        fill: false,
                        borderColor: themeColor[1],
                        label: yAxisLabel+" Graph",
                        pointBackgroundColor: function(context) {
                            let index = context.dataIndex;
                            let value = context.dataset.data[index];
                            return value > threshold ? themeColor[0] : 'transparent';
                        },
                        pointBorderColor: function(context) {
                            let index = context.dataIndex;
                            let value = context.dataset.data[index];
                            return value > threshold ? themeColor[0] : 'transparent';
                        }
                    },
                    {
                        data: thresholdLine.map(y=>{return y.y}),
                        showLine: true,
                        borderColor: themeColor[0],
                        fill: false,
                        pointBorderColor: "transparent",
                        label: "threshold = "+threshold+" "+yUnit
                    }
                ]
            },
            options: {
                animation: {
                    duration: 0
                },
                tooltips: {
                    callbacks: {
                        label: function(tooltipItem, data) {
                            let label = yAxisLabel+" : "+tooltipItem.yLabel+" F  |  "+xAxisLabel+" : "+tooltipItem.xLabel;


                            if (tooltipItem.yLabel > threshold) {
                                label += "  |  Location : "+data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].location //change this to proper key

                            }
                            return label;
                        }
                    }
                },
                scales: {
                    yAxes: [
                        {
                            scaleLabel: {
                                display: true,
                                labelString: yAxisLabel + " in " + yUnit,
                            },
                        },
                    ],
                    xAxes: [
                        {
                            scaleLabel: {
                                display: true,
                                labelString: xAxisLabel,
                            },
                        },
                    ],
                },
            }
        }

        if(this.state.sensorReadings.length === 0)
        {
            return (<h5 className="card-subtitle mb-2 text-muted " align={"center"}><i className="bi bi-lightning"/>No Readings Yet!</h5>)
        }
        else{
        return (

            <div>
                <div><big><i className={icon} style={{color: themeColor[0]}}/>{chartTitle}</big></div>
                <br/>
                <canvas
                    id="myChart"
                    ref={this.chartRef}
                />
            </div>
        )}
    }
    componentDidMount() {
        this._isMounted = true;
        if(this._isMounted){
        SensorService.getSensorReadings(apiEndPoint).then((response)=>{
            const sensorReadings = response.data
            this.setState({sensorReadings})
            const myChartRef = this.chartRef.current.getContext("2d");
            new Chart(myChartRef, options);
        });
    }
    }

    componentWillUnmount(){
        this._isMounted = false;
    }
    

}

export default SensorReadingChart;

So I want to regirect to login page without having this error Here is full error

Unhandled Rejection (TypeError): Cannot read property 'getContext' of null (anonymous function) src/components/SensorReadingChart.js:154

152 |         const sensorReadings = response.data
153 |         this.setState({sensorReadings})
> 154 |         const myChartRef = this.chartRef.current.getContext("2d");
    | ^  155 |         new Chart(myChartRef, options);
156 |     });
157 | }

View compiled This screen is visible only in development. It will not appear if the app crashes in production. Open your browser’s developer console to further inspect this error. Click the 'X' or hit ESC to dismiss this message.


Solution

  • chartRef = React.createRef();
    

    this

    this.chartRef = React.createRef();
    

    Add

    ...
    class SensorReadingChart extends Component {
    
        _isMounted = false;
        chartRef = React.createRef(); // <-- HERE!!
        constructor(props) {
            super(props);
    ...
    

    Basically, JavaScript can't have variables in directly under class like the above, that works unexpectedly. So the line above moves:

    ...
    class SensorReadingChart extends Component {
    
        constructor(props) {
            super(props);
            this._isMounted = false;  // <-- WITH IT!!
            this.chartRef = React.createRef(); // <-- HERE!!
    ...
    

    this problem is so troublesome.


    To avoid this problem, I strongly recommend functional component style, If you have special circumstance. In functional component style, instance of this is always clear.