2

I am learning reactjs and I'm trying to plot bar graph. So I'm trying to plot population race graph which change over the time and looks like this. Population race bar chart

For plotting graph I am using chart-race-react. But it seems like I'm doing something wrong in my code.

My code:

import React, { Component } from 'react';
import BarChart from 'chart-race-react';

import './App.css';
const API_URL = 'https://example.json';

const randomColor = () => {
  return `rgb(${255 * Math.random()}, ${255 * Math.random()}, ${255})`;
}

class App extends Component {
 constructor(props) {
     super(props);
     this.state = {
     countries: []
   }
 }

componentDidMount() {

fetch(API_URL)
.then(v => v.json())
.then(array => {
  const dataForVisualisation = {};

  array.forEach(element => {
      const countryName = element['Country Name'];
      if (!dataForVisualisation[countryName]) {
         dataForVisualisation[countryName] = [];
      }
      dataForVisualisation[countryName].push(element.Value);
  });

  this.setState({countries: dataForVisualisation});
})

}

render() {

 return (
    <div style={{width: "500px"}}>

     <BarChart 
      start={true}
      data = {this.state.countries}
      len = {this.state.countries[Object.keys(this.state.countries)[0]].length}
      keys = {Object.keys(this.state.countries)}
      timeout={400}
      delay={100}
      colors = {Object.keys(this.state.countries).reduce((res, item) => ({ 
        ...res, 
        ...{ [item]: randomColor() } 
    }), {})}
      labels = {Object.keys(this.state.countries).reduce((res, item, idx) => {
        return{
        ...res, 
        ...{[item]: (
          <div style={{textAlign:"center",}}>
            <div>{item}</div>
          </div>
          )}
      }}, {})}
      timeline = {Array(20).fill(0).map((itm, idx) => idx + 1)}
      timelineStyle={{
        textAlign: "center",
        fontSize: "50px",
        color: "rgb(148, 148, 148)",
        marginBottom: "100px"
      }}
      textBoxStyle={{
        textAlign: "right",
        color: "rgb(133, 131, 131)",
        fontSize: "30px",
      }}
      barStyle={{
        height: "60px",
        marginTop: "10px",
        borderRadius: "10px",
      }}
      width={[15, 75, 10]}

      maxItems = {this.state.countries.length}

    />

  </div>
    );
  }
}
export default App;

I'm encountering TypeError: this.state.countries[Object.keys(...)[0]] is undefined error because my data is undefined here. How should I change my code to make my output as shown in gif? Is there any other library for doing this ?

2 Answers 2

1

I found an issue on your code like in len property of the BarChart that gets the error Cannot read property 'length' of undefined. After I used null checking with question mark, I am not getting any error but printing 1 to 20. I am not sure it helps you or not. May be after changing that, you will get it solved.

len={this.state.countries[Object.keys(this.state.countries)[0]].length}

replaced by

len={this.state.countries[Object.keys(this.state.countries)[0]]?.length}
3
  • Thanks @Khabir but this did not solve all my problem but a part of it definitely.
    – kelte
    Commented Jun 13, 2020 at 17:28
  • You are welcome, I tried a lot to show the graph but was unable. you provided an code link on other answer where it includes conditional statement like this.state.countries.length > 0 but this.state.countries is not an array, it is an object. you must remove that condition. Please check this code link: codesandbox.io/s/unruffled-kepler-ecq0d
    – Khabir
    Commented Jun 13, 2020 at 17:34
  • Thanks for trying @Khabir.I have got this output at some point but again edited it to get expected output. If you get any idea how this graph can be plotted please ping here
    – kelte
    Commented Jun 13, 2020 at 17:37
0

You should just wait until countries is filled before trying to render anything. You can for example render BarChart component only if countries array has at least one element :

[...]
{this.state.countries.length > 0 && <BarChart 
  start={true}
  data = {this.state.countries}
  len = {this.state.countries[Object.keys(this.state.countries)[0]].length}
  keys = {Object.keys(this.state.countries)}
  timeout={400}
  delay={100}
  colors = {Object.keys(this.state.countries).reduce((res, item) => ({ 
    ...res, 
    ...{ [item]: randomColor() } 
}), {})}
  labels = {Object.keys(this.state.countries).reduce((res, item, idx) => {
    return{
    ...res, 
    ...{[item]: (
      <div style={{textAlign:"center",}}>
        <div>{item}</div>
      </div>
      )}
  }}, {})}
  timeline = {Array(20).fill(0).map((itm, idx) => idx + 1)}
  timelineStyle={{
    textAlign: "center",
    fontSize: "50px",
    color: "rgb(148, 148, 148)",
    marginBottom: "100px"
  }}
  textBoxStyle={{
    textAlign: "right",
    color: "rgb(133, 131, 131)",
    fontSize: "30px",
  }}
  barStyle={{
    height: "60px",
    marginTop: "10px",
    borderRadius: "10px",
  }}
  width={[15, 75, 10]}

  maxItems = {this.state.countries.length}

/>}

{this.state.countries.length === 0 && <span>Loading...</span>}
[...]

Note the conditional rendering syntax.

7
  • did this work and output was as given in gif ? @Florian
    – kelte
    Commented Jun 13, 2020 at 17:13
  • I'm getting a blank screen after loading
    – kelte
    Commented Jun 13, 2020 at 17:15
  • I let you test my suggestion and see if it suits your needs. I did not setup your use case. Commented Jun 13, 2020 at 17:15
  • You can setup a code sandbox, it would be simplier to help (codesandbox.io) Commented Jun 13, 2020 at 17:16
  • can you test it from your end if possible
    – kelte
    Commented Jun 13, 2020 at 17:17

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.