How do I draw chart only after promise is returned in React?

315 Views Asked by At

I am using the React wrapper for Chartjs. I use the fetch API to call the Nomics API to get data and store it in some arrays. I then want to use the arrays in a new Chartjs instance. Everything works except for the rendering of my chart and I think it is because it is rendering before the promise is complete. How would I only return the chart until after the call to the Nomics API is complete OR update it after it is complete?

Here is my component:

import React from 'react';
import {Line} from 'react-chartjs-2';

const Day = () =>{
    
    //starting price reiterated over the length of the data.
    const zeroLine = [];
    //stores all the increments of time
    const timeLabels = [];
    //List of all of the prices
    const prices = [];
    
    
    
    fetch("https://api.nomics.com/v1/currencies/sparkline?key=yadayadayada&ids=BTC&start=2020-04-14T00%3A00%3A00Z&end=2020-04-20T01%3A00%3A00Z",
    {}).then((res) => res.json())
      .then((result) => {
        
        for(var i = 0; i <= result[0].prices.length - 1; i++){
            zeroLine.push(result[0].prices[0]);
            timeLabels.push(result[0].timestamps[i]);
            prices.push(result[0].prices[i]);
            
        }
      });
    
    const chartData = {
      labels: timeLabels,
      datasets: [
        {
          label: 'Price',
          fill: false,
          lineTension: 0,
          backgroundColor: 'rgba(75,192,192,1)',
          borderColor: 'rgba(0,0,0,1)',
          borderWidth: 2,
          data: prices
        },
        {
          //Change this to create points for every time/date
          label: '0 line',
          fill: false,
          borderDash: [10,5],
          data: zeroLine,
          
          
        }
      ]
    }

    return(
    <Line
          data={chartData}
          options={{
            elements:{
                point: {
                    radius: 0
                }
            },
            title:{
              display:true,
              text:'BTC Price',
              fontSize:20
            },
            legend:{
              display:false,
              position:'right'
            }
          }}
        />
    )
}

export default Day;

2

There are 2 best solutions below

2
On BEST ANSWER

You can make use of the useState and useEffect hooks to achieve this.

The useEffect hook can act as componentDidMount(when you pass the dependency array as []), componentDidUpdate(when you pass values into the dependency array) and componentWillUnmount(when you have a third function to it)

The useState on the other hand, is similar to using setState. It triggers a component re-render whenever it changes.

In your case, what we are now doing is basically updating the state on fetch response, which is triggering the rerender

import React, {useState, useEffect} from 'react';
import {Line} from 'react-chartjs-2';

const Day = () =>{
    const [zeroLine, setZeroLine] = useState([]);
    const [timeLabels, setTimeLabels] = useState([]);
    const [prices, setPrices] = useState([]);
    
    useEffect(() => {
      fetch("https://api.nomics.com/v1/currencies/sparkline?key=yadayadayada&ids=BTC&start=2020-04-14T00%3A00%3A00Z&end=2020-04-20T01%3A00%3A00Z",
    {}).then((res) => res.json())
      .then((result) => {
        const zL = [];
        const tL = [];
        const p = [];
        for(var i = 0; i <= result[0].prices.length - 1; i++){
            zL.push(result[0].prices[0]);
            tL.push(result[0].timestamps[i]);
            p.push(result[0].prices[i]);
        }
        setZeroLine(zL);
        setTimeLabels(tL);
        setPrices(p);
      });
    }, []);
    
    const chartData = {
      labels: timeLabels,
      datasets: [
        {
          label: 'Price',
          fill: false,
          lineTension: 0,
          backgroundColor: 'rgba(75,192,192,1)',
          borderColor: 'rgba(0,0,0,1)',
          borderWidth: 2,
          data: prices
        },
        {
          //Change this to create points for every time/date
          label: '0 line',
          fill: false,
          borderDash: [10,5],
          data: zeroLine,
          
          
        }
      ]
    }

    return(
      <Line
          data={chartData}
          options={{
            elements:{
                point: {
                    radius: 0
                }
            },
            title:{
              display:true,
              text:'BTC Price',
              fontSize:20
            },
            legend:{
              display:false,
              position:'right'
            }
          }}
        />
    )
}

export default Day;

1
On

You can do this with the useState and useEffect hooks and conditional rendering:

import React, {useState, useEffect} from 'react';
import {Line} from 'react-chartjs-2';

const Day = () =>{
    // this piece of state will hold the data passed to the chart component once it returns from your fetch call.
    const [chartData, setChartData] = useState(null);
    
    
    const getChartData = () => fetch("https://api.nomics.com/v1/currencies/sparkline?key=yadayadayada&ids=BTC&start=2020-04-14T00%3A00%3A00Z&end=2020-04-20T01%3A00%3A00Z",
    {}).then((res) => res.json())
      .then((result) => {
            
        //starting price reiterated over the length of the data.
        const zeroLine = [];
        //stores all the increments of time
        const timeLabels = [];
        //List of all of the prices
        const prices = [];
        for(var i = 0; i <= result[0].prices.length - 1; i++){
            zeroLine.push(result[0].prices[0]);
            timeLabels.push(result[0].timestamps[i]);
            prices.push(result[0].prices[i]);
            
        }
        const chartData = {
          labels: timeLabels,
          datasets: [
            {
              label: 'Price',
              fill: false,
              lineTension: 0,
              backgroundColor: 'rgba(75,192,192,1)',
              borderColor: 'rgba(0,0,0,1)',
              borderWidth: 2,
              data: prices
            },
            {
              //Change this to create points for every time/date
              label: '0 line',
              fill: false,
              borderDash: [10,5],
              data: zeroLine,
          
          
            }
          ]
        }
        setChartData(chartData)
      });

    useEffect(getChartData, [])

    return chartData ? (
    <Line
          data={chartData}
          options={{
            elements:{
                point: {
                    radius: 0
                }
            },
            title:{
              display:true,
              text:'BTC Price',
              fontSize:20
            },
            legend:{
              display:false,
              position:'right'
            }
          }}
        />
    ) : <p>loading...</p>
}

export default Day;