Pacholoamit
Pacholoamit

Reputation: 265

Rendering a component only after API request is finished React hooks

I'm getting an error when my chart component renders due to its initial state which is an empty array before the API response. It would say something like Cannot read property '0' of undefined which is due to the fact that when the component loads, its initial state is, well... an empty array. So I'd like to know if there's a way to make the component render only after the API response is received.

Here is the parent component:

function InformationPage({
    match: {
        params: { symbol },
    },
}) {

    const [chartData, setChartData] = useState([]); {/*Declaring state*/}

useEffect(() => {  {/*Fetching API response here to set state*/}
    axios
        .get(
            `https://finnhub.io/api/v1/stock/candle?symbol=${symbol}&resolution=15&from=1572651390&to=1602623478&token=xxxxxxxxx`
        )
        .then((res) => {
            console.log(res.data);
            setChartData(res.data);
        })
        .catch((err) => {
            console.log(err);
        });
}, [symbol]);

return (
    
        <Grid item container xs={12} sm={12} md={6} lg={8}>
            {chartData && <QuoteChart chartData={chartData} iex={iex} />}
        </Grid>
    
   );
}
export Default InformationPage;

Here is the child chart component, basically when I do the fetch request from axios it returns an array that will get mapped in the chartRender object. I just need to find a way to render the component only AFTER the api request from axios is finished as to not have an error. Any help would be greatly appreciated.

function QuoteChart(props) {
    const classes = useStyles();
    const { chartData } = props;

    const chartRender = chartData.map((chartConfig) => ({
        x: new Date(chartConfig?.t),
        y: [chartConfig?.o, chartConfig?.h, chartConfig?.l, chartConfig?.c],
    }));

    // const chartDataLog = console.log(chartData);
    const config = {
        series: [
            {
                data: [{ chartRender }],
            },
        ],

Upvotes: 0

Views: 7675

Answers (4)

MiKo
MiKo

Reputation: 2146

Update: According to Finnhub API it's an object that you receive from the API, not an array. This means that charData.length in my previous answer will never be evaluated to true and the child component QuoteChart will never get rendered.

I'd suggest the following changes. In your parent component, make charData = null initially:

const [chartData, setChartData] = useState(null);

When the API has returned a response and chartData has been assigned a value your child component can be rendered:

{chartData && <QuoteChart chartData={chartData} iex={iex} />}

The chart you want to render in the child component expects the following input: [{ x: date, y: [O,H,L,C] }]. Because map() can only be called on arrays and chartData is not one, you should navigate to one of the arrays inside the said object, e.g. chartData.t:

const chartRender = chartData.t.map((timestamp, index) => ({
    x: new Date(timestamp),
    y: [chartData.o[index], chartData.h[index], chartData.l[index], chartData.c[index]],
}));

Finally, series.data expects an array, you don't need to additionally wrap it in an object. So this should work:

const config = {
    series: [{
        data: chartRender
    }]
};

You initialize chartData with an empty array, therefore your condition should look like this:

{chartData.length && <QuoteChart chartData={chartData} iex={iex} />}

If you expect the API to return an empty list, I'd recommend to set an undefined or null as the initial value of chartData:

const [chartData, setChartData] = useState(null);

In this case you can keep your condition as is.

Upvotes: 8

nir shabi
nir shabi

Reputation: 367

The easiest way would require a simple small change to your code:

 {chartData.length && <QuoteChart chartData={chartData} iex={iex} />}

After researching finnhub - I see that the response is not an array. I tested this url:

https://finnhub.io/api/v1/stock/candle?symbol=AAPL&resolution=15&from=1572651390&to=1602623478&token=

The response is an object with a "c" property. Your code should be modified accordingly. For the above link example:

setChartData(res.data.c);

Upvotes: 0

Radu Diță
Radu Diță

Reputation: 14211

You need to wrap your axios call in an async function and wait there

useEffect(() => {  {/*Fetching API response here to set state*/}
    
   const getInfo = async () => {
       await res = axios
        .get(
            `https://finnhub.io/api/v1/stock/candle?symbol=${symbol}&resolution=15&from=1572651390&to=1602623478&token=xxxxxxxxx`
        )
        console.log(res.data);
        setChartData(res.data);
   }

   getInfo()
}, [symbol]);

Upvotes: 0

Retosi
Retosi

Reputation: 45

If I'm not mistaken you can simply use async/await for your problem. So change the useEffect line to

useEffect(async () => {  {/*Fetching API response here to set state*/}

and then

 await axios
    .get(
        `https://finnhub.io/api/v1/stock/candle?symbol=${symbol}&resolution=15&from=1572651390&to=1602623478&token=xxxxxxxxx`
    )

Then your application should wait for the axios to finish fetching

Upvotes: 1

Related Questions