Today I wanted to test axios.all, so I made the script that you can see below only there is a small problem. The script allows me to retrieve data from the API when I run it, however there is something I don't understand. The functions setInterval execute an action every 30 seconds as requested but it displays exactly the same values and when I reload the page manually it's the same a new console.log appears but with exactly the same data. My goal is of course that once the first request is done, other requests can be done to update the data, I have the impression that the data is stored in a cache and that it never expires
Thanks for your help
Here is my script :
import logo from "./logo.svg";
import "./App.css";
import axios from "axios";
import { useEffect, useState } from "react";
const App = () => {
const [uniData, setUniDataTop] = useState([]);
const [susData, setSusDataSec] = useState([]);
const [Ptest, setPtest] = useState([]);
const fetchData = () => {
const uniAPI = "https://api.coingecko.com/api/v3/exchanges/uniswap/tickers";
const susAPI =
"https://api.coingecko.com/api/v3/exchanges/sushiswap/tickers";
const getUniPrice = axios.get(uniAPI);
const getSusPrice = axios.get(susAPI);
axios.all([getUniPrice, getSusPrice]).then(
axios.spread((...allData) => {
const priceuni = allData[0].data.tickers;
const pricesus = allData[1].data.tickers;
console.log(pricesus);
console.log(priceuni);
const unitest = priceuni?.find(
(element) =>
element.trade_url ===
"https://app.uniswap.org/#/swap?inputCurrency=ETH&outputCurrency=0xc669928185dbce49d2230cc9b0979be6dc797957"
);
const unitest2 = unitest?.converted_last.usd;
const sustest = pricesus?.find(
(element) =>
element.trade_url ===
"https://app.sushi.com/swap?inputCurrency=0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5&outputCurrency=0x6b175474e89094c44da98b954eedeac495271d0f"
);
const sustest2 = sustest?.converted_last.usd;
const unitable = [unitest2, "ETH/USDT", "UniSwap"];
const sustable = [sustest2, "ETH/USDT", "Uniswap"];
var number = [unitable, sustable];
number.sort();
const percentage = [(number[1][0] - number[0][0]) / number[1][0]] * 100;
setUniDataTop(number[1][0]);
setSusDataSec(number[0][0]);
setPtest(percentage);
})
);
};
useEffect(() => {
fetchData();
}, []);
setInterval(fetchData, 30 * 1000);
return (
<>
{uniData}
<br />
{susData}
<br />
{Ptest}%
</>
);
};
export default App;
#Konrad is correct. You can't call setInterval outside of useEffect, in your implementation on the first render setInterval run the fetch which will call the api. Then it will update the state which will in return re-render the page and then the setInterval run again and update the state and this will go on until the application crashes due to lack of memory.
Instead, you can put the setInterval inside of the useEffect. This way the setInterval is created once when the component mounts for the first time and is not created again and again when the component re-renders.
useEffect(() => {
const timeout = setInterval(() => {
fetchData();
}, 30 * 1000);
return () => {
// clears the interval when the unmounts
clearInterval(timeout);
};
}, []);
// setInterval(fetchData, 30 * 1000) // remove this
And since your data in the state is most likely number or string, you can remove the [] from the useState([]) too.
Hope this helps.
Related
I have created some state at the top level component (App), however it seems that when this state is updated, the updated state is not read by the asynchronous function defined in useEffect() (it still uses the previous value), more detail below:
I am attempting to retrieve the state of the const processing in the async function toggleProcessing defined in useEffect(), so that when processing becomes false, the async function exits from the while loop. However, it seems that when the processing updates to false, the while loop still keeps executing.
The behaviour should be as follows: Pressing the 'Begin Processing' button should console log "Processing..." every two seconds, and when that same button is pressed again (now labeled 'Stop Processing'), then "Stopping Processing" should be console logged. However, in practice, "Stopping Processing" is never console logged, and "Processing" is continuously logged forever.
Below is the code:
import React, { useState, useEffect} from 'react'
const App = () => {
const [processing, setProcessing] = useState(false)
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
useEffect(() => {
const toggleProcessing = async () => {
while (processing) {
console.log('Processing...')
await sleep(2000);
}
console.log('Stopping Processing')
}
if (processing) {
toggleProcessing() // async function
}
}, [processing])
return (
<>
<button onClick={() => setProcessing(current => !current)}>{processing ? 'Stop Processing' : 'Begin Processing'}</button>
</>
)
}
export default App;
It really just comes down to being able to read the updated state of processing in the async function, but I have not figure out a way to do this, despite reading similar posts.
Thank you in advance!
If you wish to access a state when using timeouts, it's best to keep a reference to that variable. You can achieve this using the useRef hook. Simply add a ref with the processing value and remember to update it.
const [processing, setProcessing] = useState<boolean>(false);
const processingRef = useRef(null);
useEffect(() => {
processingRef.current = processing;
}, [processing]);
Here is the working code:
import React, { useState, useEffect, useRef} from 'react'
const App = () => {
const [processing, setProcessing] = useState(false)
const processingRef = useRef(null);
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
useEffect(() => {
const toggleProcessing = async () => {
while (processingRef.current) {
console.log('Processing')
await sleep(2000);
}
console.log('Stopping Processing')
}
processingRef.current = processing;
if (processing) {
toggleProcessing() // async function
}
}, [processing])
return (
<>
<button onClick={() => setProcessing(current => !current)}>{processing ? 'Stop Processing' : 'Begin Processing'}</button>
</>
)
}
export default App;
I was interested in how this works and exactly what your final solution was based on the accepted answer. I threw together a solution based on Dan Abramov's useInterval hook and figured this along with a link to some related resources might be useful to others.
I'm curious, is there any specific reason you decided to use setTimeout and introduce async/await and while loop rather than use setInterval? I wonder the implications. Will you handle clearTimeout on clean up in the effect if a timer is still running on unmount? What did your final solution look like?
Demo/Solution with useInterval
https://codesandbox.io/s/useinterval-example-processing-ik61ho
import React, { useState, useEffect, useRef } from "react";
const App = () => {
const [processing, setProcessing] = useState(false);
useInterval(() => console.log("processing"), 2000, processing);
return (
<div>
<button onClick={() => setProcessing((prev) => !prev)}>
{processing ? "Stop Processing" : "Begin Processing"}
</button>
</div>
);
};
function useInterval(callback, delay, processing) {
const callbackRef = useRef();
// Remember the latest callback.
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
callbackRef.current();
}
if (delay !== null && processing) {
let id = setInterval(tick, delay);
console.log(`begin processing and timer with ID ${id} running...`);
// Clear timer on clean up.
return () => {
console.log(`clearing timer with ID ${id}`);
console.log("stopped");
clearInterval(id);
};
}
}, [delay, processing]);
}
export default App;
Relevant Links
Dan Abramov - Making setInterval Declarative with React Hooks
SO Question: React hooks - right way to clear timeouts and intervals
setTimeout and clearTimeout in React with Hooks (avoiding memory leaks by clearing timers on clean up in effects)
I have this React code:
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [players, setPlayers] = useState([]);
// Get all Players
const getAllPlayersUrl = "http://localhost:5087/api/GetAllPlayers";
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
}, []);
const [playerCount, setPlayerCount] = useState(players.length);
return (
<div>
<p>{`This is how many there are: ${playerCount}`}</p>
</div>
);
}
export default App;
I want to print how many initial players using playerCount variable. However it says it's zero:
This is how many there are: 0
If I instead print players.length, it would output the correct number:
<p>{`This is how many there are: ${players.length}`}</p>
This is how many there are: 9
Even if I remove dependency array to keep rendering, playerCount still wont update:
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
});
I wonder why the useState is not working? Is there something I am missing in my code?
A good rule of thumb with state (and props) is to avoid duplicating state values when a value can be determined entirely by another. Otherwise, you can run into issues like these, where keeping multiple states in sync can be more challenging than it needs to be.
Here, you set the initial value of playerCount when the component mounts:
const [playerCount, setPlayerCount] = useState(players.length);
And the component mounts only once - and at that time, players is the empty array - so playerCount becomes 0, and because you never call setPlayerCount, it always remains 0.
While you could fix it by calling setPlayerCount inside your .then, a better approach would be to either calculate the player count from the players state only when needed:
function App() {
const [players, setPlayers] = useState([]);
const getAllPlayersUrl = "http://localhost:5087/api/GetAllPlayers";
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
}, []);
return (
<div>
<p>{`This is how many there are: ${players.length}`}</p>
</div>
);
}
Or, if you really had to, to memoize the count depending on the players array (without creating additional state).
function App() {
const [players, setPlayers] = useState([]);
const playerCount = useMemo(() => players.length, [players]);
const getAllPlayersUrl = "http://localhost:5087/api/GetAllPlayers";
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
}, []);
return (
<div>
<p>{`This is how many there are: ${playerCount}`}</p>
</div>
);
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
Im new at React, I was trying to make weather website. I want to get the visitor’s IP first, then get the city location, and then get the weather conditions directly through Openweather. Here is my code, I hope someone can help me answer how to complete this website, Thank you
import { useState, useEffect } from "react";
import axios from "axios";
require("dotenv").config();
function IpGet() {
const [ip, setIP] = useState("");
const [countryName, setcountryName] = useState("");
const [cityName, setcityName] = useState("");
const [countryCode, setcountryCode] = useState("");
const [countryStateName, setcountryStateName] = useState("");
const WeatherKey = process.env.REACT_APP_WEATHERKEY;
const getData = async () => {
const res = await axios.get("https://geolocation-db.com/json/");
setIP(res.data.IPv4);
setcountryName(res.data.country_name);
setcityName(res.data.city);
setcountryCode(res.data.country_code);
setcountryStateName(res.data.state);
};
// const getWeather = async () => {
// const WeatherUrl = await axios.get(
// `https://api.openweathermap.org/data/2.5/weather?q=${cityName},${countryStateName}&appid=${WeatherKey}`
// );
// };
useEffect(() => {
getData();
}, []);
return (
<div className="IpGet">
<h4>{ip}</h4>
<h4>{countryName}</h4>
<h4>{countryCode}</h4>
<h4>{countryStateName}</h4>
<h4>{cityName}</h4>
</div>
);
}
export default IpGet;
The question is vague but here is a bit of a guess.
A few tips to start with:
You probably don't need axios for most front-end solutions. It is just an extra dependency. Use the fetch API instead.
Keep your variable names consistent - setCountryName instead of setcountryName.
The useMemo hook will prevent a function from being created on every render. You can pass the second argument of a dependency array that contains variables. If any of those variables change, useMemo will recalculate that function.
Now to the code. You can give useEffect the second argument of an array of variables. If any of these variables change, the effect will run the callback function provided as the first arg. useEffect will also always run once when the component mounts.
Create a second effect that runs when you get the data needed to make the weather API call.
All things above considered, your code might now look like this (untested):
import { useState, useEffect } from 'react';
require('dotenv').config();
function IpGet() {
const [ip, setIP] = useState('');
const [countryName, setCountryName] = useState('');
const [cityName, setCityName] = useState('');
const [countryCode, setCountryCode] = useState('');
const [countryStateName, setCountryStateName] = useState('');
const weatherKey = process.env.REACT_APP_WEATHERKEY;
// useMemo to avoid recreating this function on every render
const getData = React.useMemo(() => async () => {
const res = await fetch('https://geolocation-db.com/json/');
setIP(res.data.IPv4);
setCountryName(res.data.country_name);
setCityName(res.data.city);
setCountryCode(res.data.country_code);
setCountryStateName(res.data.state);
});
const getWeather = React.useMemo(() => async () => {
if (!cityName || !countryStateName || !weatherKey) return;
const weatherUrl = `https://api.openweathermap.org/data/2.5/weather?q=${cityName},${countryStateName}&appid=${weatherKey}`;
const weatherData = await fetch(weatherUrl);
// Do something with weatherData here... set to some state or something.
});
useEffect(() => {
getData();
}); // No dependency array, so this will only run once when the component mounts
useEffect(() => {
getWeather();
}, [cityName, countryStateName]); // This will trigger the callback when any of these variables change.
return (
<div className='IpGet'>
<h4>{ip}</h4>
<h4>{countryName}</h4>
<h4>{countryCode}</h4>
<h4>{countryStateName}</h4>
<h4>{cityName}</h4>
</div>
);
}
export default IpGet;
This question already has answers here:
React setState not updating state
(11 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
I want to make search request to the api. but problem I'm having is that every time URLSearchParams gets updated. the searchKeyword does not update. I mean it's not rerendering. and when i refresh the page i want to send the request with updated value. but I want to rerender the searchKeyword everytime the value = new URLSearchParams(window.location.search).get("query") update.
const [searchKeyword, setSearchKeyword] = useState("")
let value = new URLSearchParams(window.location.search).get("query")
useEffect(() => {
setSearchKeyword(value)
axios.get(`http://127.0.0.1:8000/music/api/searchtrack/?search=${searchKeyword}`)
.then(res => {
setSongs(res.data)
}).catch(err => {
console.log(err)
})
}, [searchKeyword])
You need something that listens to window.location, one option is to use useLocation from react-router-dom and use the location object in your useEffect.
This would be a full example based on your code
import React, { useEffect, useState } from 'react';
const mockFetch = path => {
return new Promise(res => {
setTimeout(() => {
console.log(path);
res({
path,
data: [1, 2, 3]
});
}, 500);
});
};
const useSearch = () => {
const [search, setSearch] = React.useState(window.location.search);
const listenToPopstate = () => {
const searchPath = window.location.search;
setSearch(searchPath);
};
React.useEffect(() => {
window.addEventListener('popstate', listenToPopstate);
return () => {
window.removeEventListener('popstate', listenToPopstate);
};
}, []);
return search;
};
export default function App() {
const search = useSearch();
const [songs, setSongs] = useState([]);
useEffect(() => {
let value = new URLSearchParams(search).get('query');
const g = async () => {
try {
const data = await mockFetch(
`http://127.0.0.1:8000/music/api/searchtrack/?search=${value}`
);
setSongs(data);
} catch (err) {
console.log(err);
}
};
g();
}, [search]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>{JSON.stringify(songs)}</p>
</div>
);
}
This example is not production ready, to make it production ready:
See if you can replace the useSearch by a 3rd party library you are using (are you using a router library), otherwise it's ok to use something like this.
Add a cancellable event to the get request so that it doesn't trigger setSongs when the user navigates away from the page.
This is because useEffect hook renders when only dependency array changes.You will have to put this line in side use effect hook.
let value = new URLSearchParams(window.location.search).get("query")
I am trying to create a loading component that will add a period to a div periodically, every 1000ms using setInterval in React. I am trying to cleanup setInterval using the method described in the docs.
https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1
import React, { useEffect, useState } from 'react'
const Loading = () => {
const [loadingStatus, setLoadingStatus] = useState('.')
const [loop, setLoop] = useState()
useEffect(() => {
setLoop(setInterval(() => {
console.log("loading")
setLoadingStatus(loadingStatus + ".")
}, 1000))
return function cleanup() {
console.log('cleaning up')
clearInterval(loop)
}
}, [])
return (<p>
{`Loading ${loadingStatus}`}
</p>)
}
export default Loading
However , the loadingStatus variable only updates once and the setInterval loop doesnt get cleared even after the component stops mounting. Do I have to make this using a class component?
Dependencies are our hint for React of when the effect should run, even though we set an interval and providing no dependencies [], React wont know we want to run it more then once because nothing really changes in our empty dependencies [].
To get the desired result we need to think when we want to run the effect ?
We want to run it when loadingStatus changes, so we need to add loadingStatus as our dependency because we want to run the effect every time loadingStatus changes.
We have 2 options
Add loadingStatus as our dependency.
const Loading = () => {
const [loadingStatus, setLoadingStatus] = useState(".");
const [loop, setLoop] = useState();
useEffect(
() => {
setLoop(
setInterval(() => {
console.log("loading");
setLoadingStatus(loadingStatus + ".");
}, 1000)
);
return function cleanup() {
console.log("cleaning up");
clearInterval(loop);
};
},
[loadingStatus]
);
return <p>{`Loading ${loadingStatus}`}</p>;
};
Make our effect not aware that we use loadingStatus
const Loading = () => {
const [loadingStatus, setLoadingStatus] = useState(".");
useEffect(() => {
const intervalId = setInterval(() => {
setLoadingStatus(ls => ls + ".");
}, 1000);
return () => clearInterval(intervalId);
}, []);
return <p>{`Loading ${loadingStatus}`}</p>;
};
Read more here => a-complete-guide-to-useeffect