0

I'm relatively new to react and Javascript. I'm trying to create a Doughnut component with chartjs but after two days of trying different approaches, I don't succeed in producing the Doughnet that I want. The idea is to have a doughnut with the sum value in the middle. Pretty straight forward, I thought. Rendering the doughnut with the correct labels and values I managed to do pretty easily. The problem that is keeping me awake at night is the value in the middle. Here is the data that is passed via props:

[
    {
        "account_type": "basic",
        "balance": 96548852,
        "count": 1529
    },
    {
        "account_type": "miner",
        "balance": 34532244,
        "count": 9466
    },
    {
        "account_type": "validator",
        "balance": 726277542,
        "count": 135
    },
    {
        "account_type": "community",
        "balance": 710828589,
        "count": 14
    }
]

This data set I was planning to use for two different Doughnut charts. One would use account_type and balance, the other account_type and count.

In App.jsx, the code look pretty straight forward. My api delivers the data, that's tested and confirmed. The problem is rendering the charts. Here is App.jsx:

import React, { Component } from "react";
import axios from "axios";
import { Route, Routes } from "react-router-dom";

import DataPie from "./components/DataPie";

class App extends Component {
  constructor() {
    super();

    this.state = {
      dataSet: [],
    };
  }

  componentDidMount() {
    this.getDataSet();
  }

  getDataSet = () => {
    axios
      .get(`${process.env.REACT_APP_API_SERVICE_URL}/dataSet`)
      .then((res) => {
        this.setState({ dataSet: res.data });
      })
      .catch((err) => {
        console.log(err);
      });
  };

  render() {
    return (
      <div>
        <section className="section">
          <div className="container">
            <div className="container is-max-desktop">
              <Routes>
                <Route 
                  exact 
                    path="/balances" 
                    element={
                      <div className="columns">
                        <div className="column box">
                          <DataPie 
                            dataSet={this.state.dataSet} /* same data set is passed */
                            labelElem='account_type'     /* labels for donut*/
                            valueElem='balance'          /* values for donut */
                            divider='1000000'            /* center value divider */
                            suffix=' M'                  /* suffix for center value */
                          />
                        </div>
                        <div className="column box">
                          <DataPie 
                            dataSet={this.state.dataSet} /* same data set is passed */
                            labelElem='account_type'     /* labels for donut */
                            valueElem='count'            /* values for donut */
                          />
                        </div>
                      </div>
                    }
                  />
            </div>
          </div>
        </section>
      </div>
    );
  }
}

export default App;

And the what I've been meddling with for two days now, the component:

import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
import { Doughnut } from 'react-chartjs-2';

ChartJS.register(ArcElement, Tooltip, Legend);

const DataPie = (props) => {
  const [data, setData] = useState({});
  const [plugins, setPlugins] = useState([]);

  useEffect(() => {
    let labels = [];
    let values = [];  

    props.dataSet.forEach(element => {
      labels.push(element[props.labelElem]);
      values.push(element[props.valueElem]);
    });

    setData({
      labels: labels,
      datasets: [
        {
          data: values,
          backgroundColor: [
            "rgba(255, 99, 132, 0.8)",
            "rgba(54, 162, 235, 0.8)",
            "rgba(255, 206, 86, 0.8)",
            "rgba(75, 192, 192, 0.8)",
            "rgba(153, 102, 255, 0.8)",
            "rgba(255, 159, 64, 0.8)"
          ],
          borderColor: [
            "rgba(255, 99, 132, 1)",
            "rgba(54, 162, 235, 1)",
            "rgba(255, 206, 86, 1)",
            "rgba(75, 192, 192, 1)",
            "rgba(153, 102, 255, 1)",
            "rgba(255, 159, 64, 1)"
          ],
          borderWidth: 1,
          polyline: {
            color: "gray",
            labelColor: "gray",
            formatter: (value) => `formatted ${value}`
          }
        }
      ]
    });

    let suffix = (props.suffix ? props.suffix : '' );
    let divider = (props.divider ? parseInt(props.divider) : 1 );
    let valueSum = 0;
    props.dataSet.forEach(element => {
      valueSum = valueSum + element[props.valueElem];
    });

    setPlugins([
      {
        beforeDraw: function (chart) {
          var width = chart.width,
            height = chart.height,
            ctx = chart.ctx;
          ctx.restore();
          var fontSize = (height / 160).toFixed(2);
          ctx.font = fontSize + "em sans-serif";
          ctx.textBaseline = "top";
          var text = Math.round(valueSum / divider).toString() + suffix,
            textX = Math.round((width - ctx.measureText(text).width) / 2),
            textY = height / 2;
          ctx.fillText(text, textX, textY);
          ctx.save();
        }
      }
    ]);
  }, [props]);

  const options = {
    plugins: {
      legend: { 
        display: false
      }
    }
  };

  return (
    <Doughnut 
      data={data}
      options={options} 
      plugins={plugins}
    />
  );
}

DataPie.propTypes = {
    dataSet: PropTypes.array.isRequired,
    labelElem: PropTypes.string.isRequired,
    valueElem: PropTypes.string.isRequired,
};

export default DataPie;

Problems started to occur when I added the plugins part of the Doughnut where I calculate the sum of all values in the chart and try to display them in the center of the doughnut. This code works, because I could display a static value. But when I try to add a calculated value, it never showed up on my chart.

At my last attempt I started using { useState, useEffect } but now my Doughnut doesn't even render anymore with various errors like:

  • Error: Too many re-renders. React limits the number of...
  • Uncaught TypeError: Cannot read properties of undefined (reading 'map')
  • The above error occurred in the <ForwardRef(ChartComponent)> component...

Another attempt where I did all the calculation within the component without using setState and useEffect, it looked like this:

const DataPie = (props) => {
  const labels = [];
  const values = [];
  let valueSum = 0;
  let suffix = (props.suffix ? props.suffix : '' );
  let divider = (props.divider ? parseInt(props.divider) : 1 );

  props.dataSet.forEach(element => {
    labels.push(element[props.labelElem]);
    values.push(element[props.valueElem]);
    valueSum = valueSum + element[props.valueElem];
  });

  console.log(valueSum);

  const data = [...]     /* < labels and values where inserted here */
  const options = {...}
  const plugins = {...}  /* < valueSum is inserted here */
  
  ...

The result of this code was a rendered doughnut with correct data in the pieces but the center value always resulted in 0 and 0 M. I added a console log here to see what is happening with the values and I see two times two renders, so for each component instance two renders. The first value per component was always 0 and the second render was the correct value.

So please help my peanut brain understand where I'm screwing things up... Thanks in advance.

AGI_rev
  • 129
  • 10

1 Answers1

1

The useEffect inside your DataPie component has no dependency, this means it will run on every render. And, the useEffect itself causes the component to re-render (by calling useState). So, an infinite re-render is created here.

Skul
  • 36
  • 3
  • I'm a few iterations further, without errors, but also without the center value updating correctly. I basically gave up and I'm using the Doughnut without the center value. If I ever manage to fix it, Ill post it here... – AGI_rev Nov 23 '22 at 15:35