I want to update the setState in realtime but it is giving me hard time since state update is an asynchronous process. May I request for your assistance so I can update state by the time button was clicked.
Here's my code:
const [dependentSystems, setdependentSystems] = useState([]);
const getDependentSystems = async() => {
const response = await axios.get('/GETAPI' + selectedSysID.SYSTEMID)
console.log("LIST OF DEPENDENT SYSTEM", response.data)
setdependentSystems(response.data)
}
JSX part
<IconButton>
<Icon
onClick={() => selectedSystem(row,'AddDep')}
/>
<Icon>
<IconButton>
selectedSystem
const [selectedSystemID, setselectedSystemID] = useState('');
let selectedSysID;
const selectedSystem = (row,action) =>{
selectedsysID = {...selectedSystemID, 'SYSTEMID':row.SYSTEMID}
getDependentSystems();
(action === 'AddDep') ? openModal() : openOtherModal()
}
Here's the result of console.log
I want to save the result of response.data in array on the first trigger or call of getDependentSystems in short in realtime so I can display in modal the dependent system by the time Edit button was clicked. What is happening is I need to close again the modal then edit again to display the dependent systems
Hope you can help me with this. Thank you
Trying to open the modal and expecting to see the state value you just enqueued won't ever work, due to the way React asynchronously processed enqueued state updates.
What I suggest then is to place the modal open triggering into a setTimeout call so that the function can complete and allow React to process the enqueued state update. Just enough to get the timeout callback to execute on a tick after the state update was processed, just about any timeout should be sufficient, but this is a bit hackish and obviously you will want to fine-tune this specifically to your app.
const selectedSystem = (row, action) => {
selectedsysID = {...selectedSystemID, 'SYSTEMID':row.SYSTEMID}
getDependentSystems();
setTimeout(() => {
(action === 'AddDep') ? openModal() : openOtherModal();
}, 17); // delay by about 1 minimum render cycle
};
An alternative would be to store the action in state and use an useEffect hook to issue the side-effect up opening the modal.
const [savedAction, setSavedAction] = useState();
const [dependentSystems, setDependentSystems] = useState([]);
const getDependentSystems = async (action) => {
const response = await axios.get('/GETAPI' + selectedSysID.SYSTEMID);
console.log("LIST OF DEPENDENT SYSTEM", response.data);
setDependentSystems(response.data);
setSavedAction(action);
};
useEffect(() => {
if (savedAction) {
(action === 'AddDep') ? openModal() : openOtherModal();
setSavedAction(null);
}
}, [savedAction]);
const selectedSystem = (row, action) => {
selectedsysID = {...selectedSystemID, 'SYSTEMID':row.SYSTEMID}
getDependentSystems(action);
};
Just stick the getDependentSystems as a onClick handler of a button.
const [dependentSystems, setdependentSystems] = useState([]);
const getDependentSystems = async () => {
const response = await axios.get('/GETAPI' + SYSTEMID)
console.log("LIST OF DEPENDENT SYSTEM", response.data)
setdependentSystems(response.data)
}
On the JSX part:
return <button onClick={getDependentSystems}>GET SYSTEM</button>
Related
So I am writing a product prototype in create-react-app, and in my App.js, inside the app() function, I have:
const [showCanvas, setShowCanvas] = useState(true)
This state is controlled by a button with an onClick function; And then I have a function, inside it, the detectDots function should be ran in an interval:
const runFaceDots = async (key, dot) => {
const net = await facemesh.load(...);
setInterval(() => {
detectDots(net, key, dot);
}, 10);
// return ()=>clearInterval(interval);};
And the detectDots function works like this:
const detectDots = async (net, key, dot) => {
...
console.log(showCanvas);
requestFrame(()=>{drawDots(..., showCanvas)});
}
}};
I have a useEffect like this:
useEffect(()=>{
runFaceDots(); return () => {clearInterval(runFaceDots)}}, [showCanvas])
And finally, I can change the state by clicking these two buttons:
return (
...
<Button
onClick={()=>{setShowCanvas(true)}}>
Show Canvas
</Button>
<Button
onClick={()=> {setShowCanvas(false)}}>
Hide Canvas
</Button>
...
</div>);
I checked a few posts online, saying that not clearing interval would cause state loss. In my case, I see some strange behaviour from useEffect: when I use onClick to setShowCanvas(false), the console shows that console.log(showCanvas) keeps switching from true to false back and forth.
a screenshot of the console message
you can see initially, the showCanvas state was true, which makes sense. But when I clicked the "hide canvas" button, and I only clicked it once, the showCanvas was set to false, and it should stay false, because I did not click the "show canvas" button.
I am very confused and hope someone could help.
Try using useCallback for runFaceDots function - https://reactjs.org/docs/hooks-reference.html#usecallback
And ensure you return the setInterval variable to clear the timer.
const runFaceDots = useCallback(async (key, dot) => {
const net = await facemesh.load(...);
const timer = setInterval(() => {
detectDots(net, key, dot);
}, 10);
return timer //this is to be used for clearing the interval
},[showCanvas])
Then change useEffect to this - running the function only if showCanvas is true
useEffect(()=>{
if (showCanvas) {
const timer = runFaceDots();
return () => {clearInterval(timer)}
}
}, [showCanvas])
Update: Using a global timer
let timer // <-- create the variable outside the component.
const MyComponent = () => {
.....
useEffect(()=>{
if (showCanvas) {
runFaceDots(); // You can remove const timer here
return () => {clearInterval(timer)}
} else {
clearInterval(timer) //<-- clear the interval when hiding
}
}, [showCanvas])
const runFaceDots = useCallback(async (key, dot) => {
const net = await facemesh.load(...);
timer = setInterval(() => { //<--- remove const and use global variable
detectDots(net, key, dot);
}, 10);
return timer //this is to be used for clearing the interval
},[showCanvas])
.....
}
I have a webpage where I fetch the data with async axios and then make calculations with them.
Here is the code snippet:
const FetchData = async () =>{
console.log("FETCH CALLED");
await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
.then(resp => {
setStockData(resp.data);
calculateTrend();
calculateTrendDirection();
})
}
Here, I get the error at calculateTrend() function. My question is, that this .then() should run when the response has arrived, but it seems that it runs before. Because both calculateTrend and calculateTrendDirection works with this fetched data
Edit: The error I am getting is Cannot read property 'previousClosePrice' of undefined. I am sure this exist in the object so mispelling is not a problem
Edit2: I edited my Component according to your solutions and one happens to work, the only thing is that the fetching gets to an infinite loop and fetches multiple times a second. My suspect is the dependencies in useEffect, but I am not sure what to set there.
Here is my full component:
function StockCard(props) {
const [FetchInterval, setFetchInterval] = useState(300000);
const [StockData, setStockData] = useState({});
const [TrendDirection, setTrendDirection] = useState(0);
const [Trend, setTrend] = useState(0);
const FetchData = async () =>{
console.log("FETCH CALLED");
const resp = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
setStockData(resp.data);
}
const calculateTrendDirection = () => {
console.log(StockData.lastPrice);
if(StockData.lastPrice.currentPrice > StockData.lastPrice.previousClosePrice){
setTrendDirection(1);
} else if (StockData.lastPrice.currentPrice < StockData.lastPrice.previousClosePrice){
setTrendDirection(-1);
} else {
setTrendDirection(0);
}
}
const calculateTrend = () => {
console.log(StockData.lastPrice);
var result = 100 * Math.abs( ( StockData.lastPrice.previousClosePrice - StockData.lastPrice.currentPrice ) / ( (StockData.lastPrice.previousClosePrice + StockData.lastPrice.currentPrice)/2 ) );
setTrend(result.toFixed(2));
}
useEffect(() => {
FetchData();
if(StockData.lastPrice){
console.log("LÉTEZIK A LAST PRICE")
calculateTrend();
calculateTrendDirection();
}
const interval = setInterval(() => {
FetchData();
}, FetchInterval)
return() => clearInterval(interval);
},[StockData, FetchData, FetchInterval, calculateTrend, calculateTrendDirection]);
return(
<div>
<CryptoCard
currencyName={StockData.lastPrice? StockData.name : "Name"}
currencyPrice={StockData.lastPrice? `$ ${StockData.lastPrice.currentPrice}` : 0}
icon={<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/Bitcoin.svg/2000px-Bitcoin.svg.png"/>}
currencyShortName={StockData.lastPrice? StockData.symbol : "Symbol"}
trend={StockData.lastPrice? `${Trend} %` : 0}
trendDirection={StockData.lastPrice? TrendDirection : 0}
chartData={[9200, 5720, 8100, 6734, 7054, 7832, 6421, 7383, 8697, 8850]}
/>
</div>
)
The then block is called only after the promise is fulfilled, so the data is available at that point.
From what I can see, the problem is setStockData tries to set the stockData state variable with the response, but calculateTrend and calculateTrendDirection are called before the state is set because updating state values is batched.
There are several solutions to the problem.
Solution 1:
You can call the two functions after the state is set:
setStockData(resp.data, () => {
calculateTrend();
calculateTrendDirection();
});
Solution 2:
You can use useEffect to call the functions again after the state is updated:
useEffect(() => {
if (stockData) { // or whatever validation needed
calculateTrend();
calculateTrendDirection();
}
}, [stockData]);
Solution 3:
You can pass the parameters to the method:
calculateTrend(resp.data);
calculateTrendDirection(resp.data);
The best option? I think #2, because it also makes sure that the trend and trend direction are re-calculated whenever stock data is updated (from whatever other causes).
I guess in calculateTrend you are using the data which setStockData sets to the state, if that is the case
setState is not happening right after you call the setState, if you want something to execute after correctly update the State then should look at something like this
setStockData(resp.data, () => {
calculateTrend();// this will call once the state gets changed
});
or you could use useEffect
useEffect(() => {
calculateTrend(); // this will call every time when stockData gets changed
}, [stockData])
If you are using stockData inside calculateTrend function and setStockData is an async function, move calculateTrend function to useEffect using stockData as dependency, so every time stockData is updated, calculateTrend and calculateTrendDirection will be called:
useEffect(() => {
const interval = setInterval(() => {
FetchData();
}, FetchInterval);
return() => clearInterval(interval);
}, [FetchInterval]);
useEffect(() => {
if(StockData.lastPrice){
console.log("LÉTEZIK A LAST PRICE")
calculateTrend();
calculateTrendDirection();
}
}, [StockData]);
const FetchData = async () =>{
console.log("FETCH CALLED");
const res = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`);
setStockData(resp.data);
}
I'm trying to avoid showing an alert in React Native more than once.
To do that, I am trying to update the state inside a condition which is inside a useEffect:
const [permissionStatus, setPermissionStatus] = useState('');
const [permissionsAlertShown, setPermissionsAlertShown] = useState(false);
useEffect(() => {
function handleAppStateChange() {
if (
AppState.currentState === 'active' &&
permissionStatus === 'denied' &&
!permissionsAlertShown
) {
setPermissionsAlertShown(true);
Alert.alert(
...
);
}
}
AppState.addEventListener('change', handleAppStateChange);
}, [permissionStatus, permissionsAlertShown]);
My issue is that if I navigate away from my app and come back to it, AppState.currentState changes and since setPermissionsAlertShown(true) is ignored, I am shown the alert again.
How do I handle this situation?
The answer was to create a callback function that will remove the listener. I will share if someone else ever looks for this.
const [permissionStatus, setPermissionStatus] = useState('');
const [permissionsAlertShown, setPermissionsAlertShown] = useState(false);
const handleAppStateChange = useCallback(() => {
if (
AppState.currentState === 'active' &&
permissionStatus === 'denied' &&
!permissionsAlertShown
) {
setPermissionsAlertShown(true);
Alert.alert(
...
);
}
}, [permissionStatus, permissionsAlertShown]);
useEffect(() => {
AppState.addEventListener('change', handleAppStateChange);
return () => AppState.removeEventListener('change', handleAppStateChange);
}, [handleAppStateChange]);
You just need to persist the state, with asyncStorage or some other storage.
The ideal case is to use what persisted of your action (an permission enabled, an API data saved etc).
In my component, I'm running a function that iterates through keys in state and updates properties as async functions complete. However, it looks like it's updating the state to the state as it existed prior to the function running.
This is the code for my component:
interface VideoDownloaderProps {
videos: string[];
}
const VideoDownloader: React.FC<VideoDownloaderProps> = ({ videos }) => {
const [progress, setProgress] = useState({} as { [key: string]: string });
const [isDownloading, setIsDownloading] = useState(false);
async function initialSetup(vids: string[]) {
const existingKeys = await keys();
setProgress(
vids.reduce<{ [key: string]: string }>((a, b) => {
a[b] = existingKeys.indexOf(b) > -1 ? "downloaded" : "queued";
return a;
}, {})
);
}
useEffect(() => {
initialSetup(videos);
}, [videos]);
async function download() {
setIsDownloading(true);
const existingKeys = await keys();
for (const videoUrl of videos) {
if (existingKeys.indexOf(videoUrl) === -1) {
setProgress({ ...progress, [videoUrl]: "downloading" });
const response = await fetch(videoUrl);
const videoBlob = await response.blob();
await set(videoUrl, videoBlob);
}
setProgress({ ...progress, [videoUrl]: "downloaded" });
}
setIsDownloading(false);
}
return (
<div>
<button disabled={isDownloading} onClick={download}>
Download Videos
</button>
{Object.keys(progress).map(url => (
<p key={url}>{`${url} - ${progress[url]}`}</p>
))}
</div>
);
};
Essentially, this iterates through a list of URLs, downloads them, and then sets the URL in state to "downloaded". However, the behavior I'm seeing is that the URL shifts from "queued" to "downloading" and then back to "queued" once the next URL begins downloading.
I think the culprit is this line:
setProgress({ ...progress, [videoUrl]: "downloaded" });
I think progress is always in the same state it was when download executes.
Prior to Hooks, I could pass an updater function to setState, but I'm not sure how to reuse existing state in a useState hook.
You can pass an updater function just like with setState. So, in this code, you'd run:
setProgress(progress => ({ ...progress, [videoUrl]: "downloading" }));
This will pass the current value of progress allowing you to update the state based on its current value.
I am getting some data from a database, which is an array of objects (songs). I would like to show 5 songs at a time, because there are more than 800, and the program slows down a lot if I try to load them all at once.
My react part works fine if I load the songs directly, but I am trying to create another state, that stores only 5 objects at a time, and display those 5, while I have another state called "songs", that will set it's state at the beginning, storing 800+ songs. When I try to store this 5 in another state, the program crashes.
I appreciate any help in understanding my mistake.
I have tried setting a console.log inside the useEffect that modifies the showSongs but esLint (i think it's esLint) keeps modifying the array at the end of the function, adding some code I don't want inside it.
I can't do showSongs.map, but I can do songs.map (But songs will have more than 800 songs, so I want to display showSongs).
function MySongsForm() {
const [offset, setOffset] = useState({ quantity: 0 });
const [showSongs, setShow] = useState([]);
const [songs, setSongs] = useState([]);
const [selectedSong, setSelected] = useState({ song: "" });
const [recommendations, setRecommendations] = useState([]);
useEffect(() => {
async function getData() {
const res = await requestMySongs();
setSongs(res);
}
getData();
}, [setSongs]);
async function submitSong() {
const recs = await recommendationRequest(selectedSong.song);
setRecommendations(recs.tracks);
}
function UpdateSelected(event) {
event.persist();
console.log(event);
console.log(event.target.value);
setSelected({ song: event.target.value });
}
useEffect(() => {
let newList = [];
for (let i = offset.quantity; i < offset.quantity + 5; i++) {
newList.push(songs[i]);
}
setShow(newList);
}, [offset.quantity, setSongs, songs]);
return (
<div>
<FormControl component="fieldset">
<RadioGroup>
{showSongs.map((song, index) => {
return (
<FormControlLabel
control={<Radio />}
key={index}
value={song.song_spotify_id}
name="songInput"
onClick={e => UpdateSelected(e)}
label={song.name}
/>
);
})}
</RadioGroup>
<Button onClick={() => submitSong()}>Select Song</Button>
</FormControl>
<DisplaySongs songs={recommendations} />
</div>
);
}
The error I am getting in the browser is:
TypeError: Cannot read property 'song_spotify_id' of undefined
The error I am getting at the same time in the console is:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
edit:
edit:
I have checked but I don't know what I am looking for. If I remove the following function, it works:
useEffect(() => {
let newList = [];
for (let i = offset.quantity; i < offset.quantity + 5; i++) {
newList.push(songs[i]);
}
setShow(newList);
}, [offset.quantity, setSongs, songs]);
This happens when you have an async function trying to update your state after the component has been unmounted. Code execution continues while your async handler is e.g. awaiting the response of a network request. In that time something already led to your component unmounting.
Check the component rendering your MySongsForm. Something makes it unmount before an async handler inside of it is trying to update the state.
useEffect(() => {
function setDisplay() {
let newList = [];
for (let i = offset.quantity; i < offset.quantity + 5; i++) {
newList.push(songs[i]);
}
console.log(newList);
setShow(newList);
}
if (songs.length !== 0) {
setDisplay();
}
}, [offset, songs]);
This solved it.