Search code examples
javascriptreactjsfetchreact-chartjs

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


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;


Solution

  • 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;