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

298 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 answers

2
Harsha Venkataramu 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
Ethan Lipkind 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;