UseEffect React 18 - Taking a long time to make api calls

177 views Asked by At

I am trying to make 7 api calls right away when the component mounts and get data quickly, then keep on making an API call for each of the 3 methods to getTemp, getGpu, getMemory. Although it's taking about 20 seconds to get 1 API call.

I am looking at my backend and everything work fine, the calls are received and responded correctly

I tried to remove asyc calls but that didn't work

Explained in details: Right now:

  • Refresh page -> backend API gets hit with 3 calls -> nothing is shown in the console or browser

  • 20 seconds later -> all my state arrays are empty

  • Circa 25 seconds later -> arrays have 1 entry

  • 1 second later -> arrays start to have multiple entries as they should and works fine

What I want to happen:

  • Hit the refresh button -> Server API needs to be hit 7 times
  • Right after that make a call every 1 second all the time
  • Don't want to wait 20 or 25 seconds for first array to populate

From Network tabs I keep getting all the API calls pending:

API Calls pending for many seconds

Eventually it clears up but it takes between 2 and 3 mins to start clearing up

Cleared API Calls

Can you help with making the 7 API calls once only and making 1 api call every second?

import { Card, Metric, CategoryBar, Flex, Text, AreaChart, ProgressBar, Title, LineChart } from "@tremor/react";
import card_1 from '../assets/cards/card_1.json'
import card_2 from '../assets/cards/card_2.json'
import card_3 from '../assets/cards/card_3.json'
import card_4 from '../assets/cards/card_4.json'
import React, { useState, useEffect } from 'react';
import axios from 'axios';

var all_cards_array = [card_1, card_2, card_3, card_4]
var array_of_temperatures = []
var array_of_gpu_usage = []
var array_of_gpu_memory = []
const array_of_colors = [
    "slate",
    "gray",
    "zinc",
    "neutral",
    "stone",
    "red",
    "orange",
    "amber",
    "yellow",
    "lime",
    "green",
    "emerald",
    "teal",
    "cyan",
    "sky",
    "blue",
    "indigo",
    "violet",
    "purple",
    "fuchsia",
    "pink",
    "rose"
];

var startTime, endTime;

var optionCelsius = {
    style: 'unit',
    unit: 'celsius'
};

var optionPercent = {
    style: 'percent',
    minimumFractionDigits: 0,
    maximumFractionDigits: 0
};

axios.defaults.baseURL = 'http://url';
const valueFormatterPercentage = (number) => `${new Intl.NumberFormat("en-US", optionPercent).format(number / 100).toString()}`;
const valueFormatterCelcius = (number) => `${new Intl.NumberFormat("en-US", optionCelsius).format(number).toString()}`;


const Dashboard = () => {
    const [temperatures, setTemperatures] = useState(null);
    const [gpuUsage, setGpuUsage] = useState(null);
    const [gpuMemory, setGpuMemory] = useState(null);
    const [count, setCount] = useState(10);

    const getTemps = async () => {
        try {
            const { data } = await axios.get('/temperature_info');
            await setTemperatures(data);
            converTemperatureJsonToArray()
            console.log('Temps')
            console.log(array_of_temperatures)
            console.log(temperatures)

            return data
        } catch (err) {
            console.error(err);
        }
    };

    const getGpuUsage = async () => {
        try {
            const { data } = await axios.get('/gpu_usage_info');
            await setGpuUsage(data);
            converGpuJsonToArray()
            console.log('Gpu Usage')
            console.log(array_of_gpu_usage)
            console.log(gpuUsage)

            return data
        } catch (err) {
            console.error(err);
        }
    };

    const getGpuMemory = async () => {
        try {
            const { data } = await axios.get('/memory_usage_info');
            await setGpuMemory(data);
            converMemoryJsonToArray()
            console.log('Memory')
            console.log(array_of_gpu_memory)
            console.log(gpuMemory)

            return data
        } catch (err) {
            console.error(err);
        }
    };

    const countDown = () => {
        const interval = setInterval(function () {
            setCount((prev) => prev - 1);
        }, 1000);
    };

    useEffect(() => {
        getTemps()
        getGpuUsage()
        getGpuMemory()
        gpuUsageKeys()
        gpuMemoryKeys()
        temperaturesKeys()
        // countDown()

    }, [temperatures, gpuUsage, gpuMemory]);


    function start() {
        startTime = new Date();
    }

    function end() {
        endTime = new Date();
        var timeDiff = endTime - startTime; //in ms
        // strip the ms
        timeDiff /= 1000;

        // get seconds 
        var seconds = Math.round(timeDiff);
        console.log(seconds + " seconds");
    }

    function select_random_color(array_lenght) {
        const list_colors = []
        for (i in array_lenght) {
            list_colors.push(array_of_colors[Math.floor(Math.random() * array_lenght)])
        }

        return list_colors
    }

    function temperaturesKeys() {
        if (temperatures) {
            console.log('In keys temperatures')
            var list_keys = []

            for (const key of Object.keys(temperatures)) {
                if (key !== 'time_stamp') {
                    list_keys.push(key)
                }
            }

            return list_keys
        }

        return null
    }

    function gpuUsageKeys() {
        if (gpuUsage) {
            console.log('In keys gpu')
            var list_keys = []

            for (const key of Object.keys(gpuUsage)) {
                if (key !== 'time_stamp') {
                    list_keys.push(key)
                }
            }

            return list_keys
        }

        return null
    }

    function gpuMemoryKeys() {
        if (gpuMemory) {
            console.log('In keys memory')
            var list_keys = []

            for (const key of Object.keys(gpuMemory)) {
                if (key !== 'time_stamp') {
                    list_keys.push(key)
                }
            }

            return list_keys
        }

        return null
    }

    function getTemperatureFormattedJson() {
        const currentDate = new Date().toLocaleTimeString()
        const dict = {}

        for (const key of Object.keys(temperatures)) {
            if (key === 'time_stamp') {
                dict[key] = currentDate
            } else {
                dict[key] = temperatures[key]
            }
        }

        return dict
    }

    function getGpuFormattedJson() {
        const currentDate = new Date().toLocaleTimeString()
        const dict = {}

        for (const key of Object.keys(gpuUsage)) {
            if (key === 'time_stamp') {
                dict[key] = currentDate
            } else {
                dict[key] = gpuUsage[key]
            }
        }

        return dict
    }

    function getMemoryFormattedJson() {
        const currentDate = new Date().toLocaleTimeString()
        const dict = {}

        for (const key of Object.keys(gpuMemory)) {
            if (key === 'time_stamp') {
                dict[key] = currentDate
            } else {
                dict[key] = gpuMemory[key]
            }
        }

        return dict
    }

    function converTemperatureJsonToArray() {
        if (temperatures) {
            if (array_of_temperatures.length > 7) {
                array_of_temperatures.splice(0, 1)
            }

            array_of_temperatures.push(getTemperatureFormattedJson())

            return array_of_temperatures
        }

        return [""]
    }

    function converGpuJsonToArray() {
        if (gpuUsage) {
            if (array_of_gpu_usage.length > 7) {
                array_of_gpu_usage.splice(0, 1)
            }

            array_of_gpu_usage.push(getGpuFormattedJson())

            return array_of_gpu_usage
        }

        return [""]
    }

    function converMemoryJsonToArray() {
        if (gpuMemory) {
            if (array_of_gpu_memory.length > 7) {
                array_of_gpu_memory.splice(0, 1)
            }

            array_of_gpu_memory.push(getMemoryFormattedJson())

            return array_of_gpu_memory
        }

        return [""]
    }

    function renderBox() {
        return (
            <div className={color}>
            </div>
        )
    }

    function uuidv4() {
        return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
        );
    }

    function renderGPUName(cardName) {
        return (
            <div key={uuidv4()}>
                <h2 className='text-2xl font-bold'>
                    Gpu Name
                </h2>
                <br />
                <Card className="max-w-xs mx-auto" decoration="top" decorationColor="indigo">
                    <Metric>{cardName}</Metric>
                </Card>
            </div>
        )
    }

    function renderCards() {
        const cards = []
        var div_top_black = 'black'
        var div_top_blue = 'grid grid-cols-5 gap-14 blue'
        const list_colors = [div_top_black, div_top_blue]
        var count = 0

        for (const card of all_cards_array) {
            var color = ''

            if (count == 0) {
                color = list_colors[0]
                count += 1
            } else {
                color = list_colors[1]
                count = 0
            }

            cards.push(
                <div key={uuidv4()}>
                    <br />
                    <br />
                    <br />
                    <br />
                </div>
            )
            cards.push(
                <div className={color} key={uuidv4()}>
                </div>
            )
            cards.push(renderGPUName(card.name))
            cards.push(
                <div key={uuidv4()}>
                    <br />
                    <br />
                    <br />
                    <br />
                </div>
            )
            cards.push(
                <div className='grid grid-cols-5 gap-14' key={uuidv4()}>
                    <div>
                        <h2 className='text-2xl font-bold'>
                            Driver Version
                        </h2>
                        <br />
                        <Card className="max-w-xs mx-auto" decoration="top" decorationColor="indigo">
                            <Metric>{card.driver_version}</Metric>
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-2xl font-bold'>
                            P State
                        </h2>
                        <br />
                        <Card className="max-w-xs mx-auto" decoration="top" decorationColor="indigo">
                            <Metric>{card.pstate}</Metric>
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-lg font-bold'>
                            Pcie Link Gen Max
                        </h2>
                        <br />
                        <Card className="max-w-xs mx-auto" decoration="top" decorationColor="indigo">
                            <Metric>{card.pcie_link_gen_max}</Metric>
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-lg font-bold'>
                            Pcie Link Gen Current
                        </h2>
                        <br />
                        <Card className="max-w-xs mx-auto" decoration="top" decorationColor="indigo">
                            <Metric>{card.pcie_link_gen_current}</Metric>
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-2xl font-bold'>
                            Temperature
                        </h2>
                        <br />
                        <Card className="max-w-sm mx-auto">
                            <Flex>
                                <Text>0 &bull; {card.temperature_gpu}C</Text>
                                <Text>200</Text>
                            </Flex>
                            <ProgressBar value={card.temperature_gpu} color="red" className="mt-3" />
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-2xl font-bold'>
                            Utilization Gpu
                        </h2>
                        <br />
                        <Card className="max-w-sm mx-auto">
                            <Flex>
                                <Text>0 &bull; {card["utilization_gpu [%]"]} %</Text>
                                <Text>100</Text>
                            </Flex>
                            <ProgressBar value={card["utilization_gpu [%]"]} color="red" className="mt-3" />
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-xl font-bold'>
                            Utilization Memory
                        </h2>
                        <br />
                        <Card className="max-w-sm mx-auto">
                            <Flex>
                                <Text>0 &bull; {card["utilization_memory [%]"]}%</Text>
                                <Text>100</Text>
                            </Flex>
                            <ProgressBar value={card["utilization_memory [%]"]} color="red" className="mt-3" />
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-2xl font-bold'>
                            Memory Total
                        </h2>
                        <br />
                        <Card className="max-w-sm mx-auto">
                            <Flex>
                                <Text>0 &bull; {card["memory_total [MiB]"]} MiB</Text>
                                <Text>{card["memory_total [MiB]"]}</Text>
                            </Flex>
                            <ProgressBar value={card["memory_total [MiB]"]} color="red" className="mt-3" />
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-2xl font-bold'>
                            Memory Free
                        </h2>
                        <br />
                        <Card className="max-w-sm mx-auto">
                            <Flex>
                                <Text>0 &bull; {card["memory_free [MiB]"]} MiB</Text>
                                <Text>{card["memory_total [MiB]"]}</Text>
                            </Flex>
                            <ProgressBar value={card["memory_free [MiB]"] / card["memory_total [MiB]"] * 100} color="red" className="mt-3" />
                        </Card>
                    </div>
                    <div>
                        <h2 className='text-2xl font-bold'>
                            Memory Used
                        </h2>
                        <br />
                        <Card className="max-w-sm mx-auto">
                            <Flex>
                                <Text>0 &bull; {card["memory_used [MiB]"]} MiB</Text>
                                <Text>{card["memory_total [MiB]"]}</Text>
                            </Flex>
                            <ProgressBar value={card["memory_used [MiB]"] / card["memory_total [MiB]"] * 100} color="green" className="mt-3" />
                        </Card>
                        <br />
                        <br />
                    </div>
                </div>
            )
        }

        return cards
    }


    return (
        <>
            <div className='grid grid-cols-1 gap-14'>
                {array_of_temperatures.length > 7 ? 'Fetched' : 'Loading... ' + count + ' seconds left'}
                <br />
                <br />
                <br />
                <br />
                <Card>
                    <Title>Cards Temperature</Title>
                    <LineChart
                        className="mt-6"
                        data={array_of_temperatures.length > 7 ? array_of_temperatures : null}
                        index="time_stamp"
                        categories={temperatures ? temperaturesKeys() : []}
                        colors={array_of_temperatures > 0 ? select_random_color(temperaturesKeys().length - 1) : ["neutral", "indigo", "orange", "green"]}
                        valueFormatter={valueFormatterCelcius}
                        yAxisWidth={100}
                    />
                </Card>
            </div>
            <br />
            <br />
            <div className='grid grid-cols-1 gap-14'>
                <Card>
                    <Title>Cards Gpu Usage</Title>
                    <LineChart
                        className="mt-6"
                        data={array_of_gpu_usage.length > 7 ? array_of_gpu_usage : null}
                        index="time_stamp"
                        categories={gpuUsage ? gpuUsageKeys() : []}
                        colors={array_of_gpu_usage > 0 ? select_random_color(gpuUsageKeys().length - 1) : ["neutral", "indigo", "orange", "green"]}
                        valueFormatter={valueFormatterPercentage}
                        yAxisWidth={100}
                    />
                </Card>
            </div>
            <br />
            <br />
            <div className='grid grid-cols-1 gap-14'>
                <Card>
                    <Title>Cards Memory Usage</Title>
                    <LineChart
                        className="mt-6"
                        data={array_of_gpu_memory.length > 7 ? array_of_gpu_memory : null}
                        index="time_stamp"
                        categories={gpuMemory ? gpuMemoryKeys() : []}
                        colors={array_of_gpu_memory > 0 ? select_random_color(gpuMemoryKeys().length - 1) : ["neutral", "indigo", "orange", "green"]}
                        valueFormatter={valueFormatterPercentage}
                        yAxisWidth={100}
                    />
                </Card>
            </div>
            <br />
            <br />
            {renderCards()}
        </>
    )
};

export default Dashboard;
2

There are 2 answers

0
Brain Bytes On BEST ANSWER

I think I had to step back and see a different approach to this. I am adding below some references to justify my response.

First, we need to break the "If the only tool you have is a hammer, you tend to see every problem as a nail"

So when to use api calls and webhooks?

This is a really good article that set me on the right path: Webhooks, Api Calls, Push Sub, and Websockets differences

Second, let's implement the websocket:

const WS_URL_ALL_INFO = 'ws://YOUR_WEBSOCKET_URL';
const socketState = 'OPEN'

const { sendJsonMessage, lastJsonMessage } = useWebSocket(WS_URL_ALL_INFO, {
    share: true,
})

    useEffect(() => {
    sendJsonMessage({ "message": socketState })

    if (lastJsonMessage) {
        console.log(lastJsonMessage)
    }
}, [jsonMessage, lastJsonMessage])

That's it! all it needed it's a websocket, you can use a setTimeout to set a delay if you want and you can use multiple useEffect() with [] to run once multiple times if you want

To @Leroy you are using polling, which in this case is the wrong approach, I know I was thinking the same way, then it hit me. Polling will always be slow, webhooks will not work either as it will be slow too since there is a call every 1 second. A websocket is opened and stays open until you close it, continuously gives you data and you can either establish a timeout server or client side. In this case it works perfectly.

Also:

ReactDOM.createRoot(
  document.getElementById("root")
).render( <div>
  <Widget name="Temperature" delay="200" />
    <Widget name="GpuUsage" delay="400" />
  </div>
);

In react 18 using the dom directly is deprecated

ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it’s running React 17. Learn more: https://reactjs.org/link/switch-to-createroot

React 18

7
Leroy On

First you are making a few big mistakes. Your useEffect will blow up if you use it like this.

If one of those variables temperatures, gpuUsage or gpuMemory will change, the Dashboard component will rerender itself. This causes all code to execute again and again. That's probably why you see what you see.

You either make each of your cards stateful, by fetching the data in the cards instead. Or you need to take a look at something like Promise.all. Although I think you need to make your individual cards statefull like <TemperatureCard> and <GpuUsageCard>, so the cards individually will fetch their data and don't care about the others. Or you could make a generic card like this.

I've added some delays to show you how network delay would look like.

const {useState,useEffect} = React;

const loadData = (data, delay) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(data), delay);
  });
}

const Widget = ({name, delay}) => {
  const [data, setData] = useState(null);
  
  const fetchData = () => {
    setData(null);
    const fetched = loadData({foo: `Data fetched for ${name}`}, delay);
    fetched.then(setData);
    setTimeout(fetchData, 1000);
  }
  
  useEffect(() => {
    fetchData();
  }, [name])
  
  
  if (null === data) {
     return <div>Loading...</div>
  }

  return <div>{data.foo}</div>;
}


ReactDOM.createRoot(
  document.getElementById("root")
).render( <div>
  <Widget name="Temperature" delay="200" />
    <Widget name="GpuUsage" delay="400" />
  </div>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Don't forget to add an AbortController during your interval. Your network might be slower to respond and will cause an old api call to be rendered. So you have to abort it. It's probably documented in the axios documentation how to do that.

You also mention that your api takes seconds to respond. It could be that your React application is requesting too many data which will overload your application. The React components will be as fast as your api calls are.