Call API every 30 seconds in ReactJS [duplicate] - reactjs

I have to monitoring some data update info on the screen each one or two seconds.
The way I figured that was using this implementation:
componentDidMount() {
this.timer = setInterval(()=> this.getItems(), 1000);
}
componentWillUnmount() {
this.timer = null;
}
getItems() {
fetch(this.getEndpoint('api url endpoint'))
.then(result => result.json())
.then(result => this.setState({ items: result }));
}
Is this the correct approach?

Well, since you have only an API and don't have control over it in order to change it to use sockets, the only way you have is to poll.
As per your polling is concerned, you're doing the decent approach. But there is one catch in your code above.
componentDidMount() {
this.timer = setInterval(()=> this.getItems(), 1000);
}
componentWillUnmount() {
this.timer = null; // here...
}
getItems() {
fetch(this.getEndpoint('api url endpoint'))
.then(result => result.json())
.then(result => this.setState({ items: result }));
}
The issue here is that once your component unmounts, though the reference to interval that you stored in this.timer is set to null, it is not stopped yet. The interval will keep invoking the handler even after your component has been unmounted and will try to setState in a component which no longer exists.
To handle it properly use clearInterval(this.timer) first and then set this.timer = null.
Also, the fetch call is asynchronous, which might cause the same issue. Make it cancelable and cancel if any fetch is incomplete.
I hope this helps.

Although an old question it was the top result when I searched for React Polling and didn't have an answer that worked with Hooks.
// utils.js
import React, { useState, useEffect, useRef } from 'react';
export const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
You can then just import and use.
// MyPage.js
import useInterval from '../utils';
const MyPage = () => {
useInterval(() => {
// put your interval code here.
}, 1000 * 10);
return <div>my page content</div>;
}

You could use a combination of setTimeout and clearTimeout.
setInterval would fire the API call every 'x' seconds irrespective whether the previous call succeeded or failed. This can eat into your browser memory and degrade performance over time. Moreover, if the server is down, setInterval would continue to bombard the server not knowing its down status.
Whereas,
You could do a recursion using setTimeout. Fire a subsequent API call, only if the previous API call succeed. If previous call has failed, clear the timeout and do not fire any further calls. if required, alert the user on failure. Let the user refresh the page to restart this process.
Here is an example code:
let apiTimeout = setTimeout(fetchAPIData, 1000);
function fetchAPIData(){
fetch('API_END_POINT')
.then(res => {
if(res.statusCode == 200){
// Process the response and update the view.
// Recreate a setTimeout API call which will be fired after 1 second.
apiTimeout = setTimeout(fetchAPIData, 1000);
}else{
clearTimeout(apiTimeout);
// Failure case. If required, alert the user.
}
})
.fail(function(){
clearTimeout(apiTimeout);
// Failure case. If required, alert the user.
});
}

#AmitJS94, there's a detailed section on how to stop an interval that adds onto the methods that GavKilbride mentioned in this article.
The author says to add a state for a delay variable, and to pass in "null" for that delay when you want to pause the interval:
const [delay, setDelay] = useState(1000);
const [isRunning, setIsRunning] = useState(true);
useInterval(() => {
setCount(count + 1);
}, isRunning ? delay : null);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
Definitely read the article to get a better understanding of the details -- it's super thorough and well-written!

As Vasanth mention, I preferred to:
use setTimeout to measure the time between the end of the last request and the beginning of the next one
make the first request straight away, not after the delay
inspired by the answer from #KyleMit https://stackoverflow.com/a/64654157/343900
import { useEffect, useRef } from 'react';
export const useInterval = (
callback: Function,
fnCondition: Function,
delay: number,
) => {
const savedCallback = useRef<Function>();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
let id: NodeJS.Timeout;
const tick = async () => {
try {
const response =
typeof savedCallback.current === 'function' &&
(await savedCallback.current());
if (fnCondition(response)) {
id = setTimeout(tick, delay);
} else {
clearTimeout(id);
}
} catch (e) {
console.error(e);
}
};
tick();
return () => id && clearTimeout(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [delay]);
};
WORKS: Using fnCondition inside which can be a condition based on the response from the last request.
//axios-hooks
const {
data,
isLoadingData,
getData,
} = api.useGetData();
const fnCondition = (result: any) => {
const randomContidion = Math.random();
//return true to continue
return randomContidion < 0.9;
};
useInterval(() => getData(), fnCondition, 1000);
DOES NOT WORK: Passing delay as null to stop useInterval like this does not work for me
with this code: https://www.aaron-powell.com/posts/2019-09-23-recursive-settimeout-with-react-hooks/
(You might get the impression it works, but after a few starts/stops it breaks)
const [isRunning, setIsRunning] = useState(true);
const handleOnclick = () => {
setIsRunning(!isRunning);
};
useInterval(() => getData(), isRunning ? 1000 : null);
<button onClick={handleOnclick}>{isRunning ? 'Stop' : 'Start'}</button>
Sum up: I'm able to stop useInterval by passing fnCondition, but not by passing delay=null

Here's a simple, full solution, that:
Polls every X seconds
Has the option of increasing the timeout each time the logic runs so you don't overload the server
Clears the timeouts when the end user exits the component
//mount data
componentDidMount() {
//run this function to get your data for the first time
this.getYourData();
//use the setTimeout to poll continuously, but each time increase the timer
this.timer = setTimeout(this.timeoutIncreaser, this.timeoutCounter);
}
//unmounting process
componentWillUnmount() {
this.timer = null; //clear variable
this.timeoutIncreaser = null; //clear function that resets timer
}
//increase by timeout by certain amount each time this is ran, and call fetchData() to reload screen
timeoutIncreaser = () => {
this.timeoutCounter += 1000 * 2; //increase timeout by 2 seconds every time
this.getYourData(); //this can be any function that you want ran every x seconds
setTimeout(this.timeoutIncreaser, this.timeoutCounter);
}

Here is a simple example using hooks in function component and this will refresh your data in a set interval.
import React from 'react';
import { useEffect, useState } from 'react';
export default function App() {
let [jokes, setJokes] = useState('Initial');
async function fetchJokes() {
let a = await fetch('https://api.chucknorris.io/jokes/random');
let b = await a.json();
setJokes(b.value);
}
// Below function works like compomentWillUnmount and hence it clears the timeout
useEffect(() => {
let id = setTimeout(fetchJokes, 2000);
return () => clearTimeout(id);
});
return <div>{jokes}</div>;
}
or, you can use axios as well to make the API calls.
function App() {
const [state, setState] = useState("Loading.....");
function fetchData() {
axios.get(`https://api.chucknorris.io/jokes/random`).then((response) => {
setState(response.data.value);
});
}
useEffect(() => {
console.log("Hi there!");
let timerId = setTimeout(fetchData, 2000);
return ()=> clearInterval(timerId);
});
return (
<>
This component
<h3>{state}</h3>
</>
);
}

Related

React jest hook testing: waitFor internal async code

I have the following hook:
import { useEffect, useRef, useState } from "react";
function useAsyncExample() {
const isMountedRef = useRef(false);
const [hasFetchedGoogle, setHasFetchedGoogle] = useState(false);
useEffect(() => {
if (!isMountedRef.current) {
isMountedRef.current = true;
const asyncWrapper = async () => {
await fetch("https://google.com");
setHasFetchedGoogle(true);
};
asyncWrapper();
}
}, []);
return hasFetchedGoogle;
}
With the following jest test (using msw and react-hooks testing library):
import { act, renderHook } from "#testing-library/react-hooks";
import { rest } from "msw";
import mswServer from "mswServer";
import useAsyncExample from "./useAsyncExample";
jest.useFakeTimers();
describe("using async hook", () => {
beforeEach(() =>
mswServer.use(
rest.get("https://google.com/", (req, res, ctx) => {
return res(ctx.json({ success: ":)" }));
})
)
);
test("should should return true", async () => {
const { result, waitFor, waitForNextUpdate, waitForValueToChange } = renderHook(() => useAsyncExample());
// ... things I tried
});
});
And I am simply trying to wait for the setHasFetchedGoogle call.
I tried multiple things:
await waitForNextUpdate(); // failed: exceeded timeout of max 5000 ms
await waitForValueToChange(() => result.current[1]); // failed: exceeded timeout of max 5000 ms
await waitFor(() => result.current[1]) // failed: exceeded timeout of max 5000 ms
The closest I have come so far is the with the following:
const spy = jest.spyOn(global, "fetch");
// ...
await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
expect(spy).toHaveBeenLastCalledWith("https://google.com");
But even this ends right before the setHasFetchedGoogle call happens, since it only await the fetch.
Online I found plenty of examples for component, where you can waitFor an element or text to appear. But this is not possible with hooks, since I am not rendering any DOM elements.
How can I listen to internal async logic of my hook? I though the waitForNextUpdate has exactly that purpose, but it doesn't work for me.
Any help is appreciated!
Actually it turns out my example works as I wanted it to.
The problem is that in the real-world case I have, the custom hook is more complex and has other logic inside which uses setTimeouts. Therefore I had jest.useFakeTimers enabled.
Apparently jest.useFakeTimers doesn't work together with waitForNextUpdate.
See more info
I will now try to get my tests to work by enabling/disabling the fakeTimers when I need them
As you said in your answer, you are using jest.useFakeTimers, but you are incorrect to say it doesn't work with waitForNextUpdate because it does.
Here is an example. I've modified your request to google to simply be an asynchronous event by waiting for two seconds. Everything should be the same with an actual mocked request though.
const wait = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay))
function useAsyncExample() {
const isMountedRef = useRef(false);
const [hasFetchedGoogle, setHasFetchedGoogle] = useState(false);
useEffect(() => {
if (!isMountedRef.current) {
isMountedRef.current = true;
const asyncWrapper = async () => {
await wait(200);
setHasFetchedGoogle(true);
};
asyncWrapper();
}
}, []);
return hasFetchedGoogle;
}
// The test, which assumes a call to jest.useFakeTimers occurred in some beforeEach.
it('should should return true', async () => {
const { result, waitForNextUpdate } = renderHook(() => useAsyncExample())
expect(result.current).toBe(false)
act(() => {
jest.advanceTimersByTime(200)
})
await waitForNextUpdate()
expect(result.current).toBe(true)
})

App Does Not Return IP Address Info in the First Call of useEffect Hook

I will briefly explain what I tried to achieve.
The code should run every 5 seconds regardless of the case the web page is refreshed or not. I achieved this and there is no problem in my code. So I used localstorage to keep track of how much time passed. Regardless of the page refreshed or not, the time passed can be tracked by the app.
Only problem is, After the first 5 seconds, in the first run, fetchData() function returns an empty object. Only after the second call of the hook (After 10 secs) it starts fetching the data result as an object. After 10 seconds there is no problem with the app. How can I make fetchData() to return results in the first run?
By the way fetchData() is a function which returns the user ip, visiting time and country data to the console.
Can you please help with this issue?
import React, { useState, useEffect } from "react";
function App() {
const [visitorInfo, setVisitorInfo] = useState({});
const fetchData = async () => {
const response = await fetch("https://www.myexternalip.com/json");
const data = await response.json();
const resGeo = await fetch(`https://ipwhois.app/json/${data.ip}`);
const dataGeo = await resGeo.json();
const date = new Date().toLocaleString("EN-CA", {
timeZone: "America/New_York"
});
setVisitorInfo((prevValue) => {
return {
...prevValue,
ip: data.ip,
country: dataGeo.country,
date: date
};
});
console.log("Data fetched!");
};
const [localStorageTime, setLocalStorageTime] = useState(
localStorage.getItem("localTime")
);
const timeInterval = 5000;
useEffect(() => {
const interval = setInterval(() => {
setLocalStorageTime(localStorage.getItem("localTime"));
if (new Date().getTime() - localStorageTime > timeInterval) {
localStorage.setItem("localTime", new Date().getTime());
setLocalStorageTime(localStorage.getItem("localTime"));
fetchData();
}
console.log(visitorInfo);
}, timeInterval);
return () => clearInterval(interval);
});
return <div></div>;
}
export default App;
You can also find the sandbox version of the code here to tinker
https://codesandbox.io/s/crazy-merkle-x0lmw5?file=/src/App.js:0-1322
Link consists of the code that I already gave above.
Thanks.

useEffect: Can't perform a React state update on an unmounted component [duplicate]

When fetching data I'm getting: Can't perform a React state update on an unmounted component. The app still works, but react is suggesting I might be causing a memory leak.
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."
Why do I keep getting this warning?
I tried researching these solutions:
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
but this still was giving me the warning.
const ArtistProfile = props => {
const [artistData, setArtistData] = useState(null)
const token = props.spotifyAPI.user_token
const fetchData = () => {
const id = window.location.pathname.split("/").pop()
console.log(id)
props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
.then(data => {setArtistData(data)})
}
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [])
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
}
Edit:
In my api file I added an AbortController() and used a signal so I can cancel a request.
export function spotifyAPI() {
const controller = new AbortController()
const signal = controller.signal
// code ...
this.getArtist = (id) => {
return (
fetch(
`https://api.spotify.com/v1/artists/${id}`, {
headers: {"Authorization": "Bearer " + this.user_token}
}, {signal})
.then(response => {
return checkServerStat(response.status, response.json())
})
)
}
// code ...
// this is my cancel method
this.cancelRequest = () => controller.abort()
}
My spotify.getArtistProfile() looks like this
this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
return Promise.all([
this.getArtist(id),
this.getArtistAlbums(id,includeGroups,market,limit,offset),
this.getArtistTopTracks(id,market)
])
.then(response => {
return ({
artist: response[0],
artistAlbums: response[1],
artistTopTracks: response[2]
})
})
}
but because my signal is used for individual api calls that are resolved in a Promise.all I can't abort() that promise so I will always be setting the state.
For me, clean the state in the unmount of the component helped.
const [state, setState] = useState({});
useEffect(() => {
myFunction();
return () => {
setState({}); // This worked for me
};
}, []);
const myFunction = () => {
setState({
name: 'Jhon',
surname: 'Doe',
})
}
Sharing the AbortController between the fetch() requests is the right approach.
When any of the Promises are aborted, Promise.all() will reject with AbortError:
function Component(props) {
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
const ac = new AbortController();
Promise.all([
fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
]).then(() => setFetched(true))
.catch(ex => console.error(ex));
return () => ac.abort(); // Abort both fetches on unmount
}, []);
return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>
For example, you have some component that does some asynchronous actions, then writes the result to state and displays the state content on a page:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
Let's say that user clicks some link when doVeryLongRequest() still executes. MyComponent is unmounted but the request is still alive and when it gets a response it tries to set state in lines (1) and (2) and tries to change the appropriate nodes in HTML. We'll get an error from subject.
We can fix it by checking whether compponent is still mounted or not. Let's create a componentMounted ref (line (3) below) and set it true. When component is unmounted we'll set it to false (line (4) below). And let's check the componentMounted variable every time we try to set state (line (5) below).
The code with fixes:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
const componentMounted = useRef(true); // (3) component is mounted
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
if (componentMounted.current){ // (5) is component still mounted?
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}
return () => { // This code runs when component is unmounted
componentMounted.current = false; // (4) set it to false when we leave the page
}
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
Why do I keep getting this warning?
The intention of this warning is to help you prevent memory leaks in your application. If the component updates it's state after it has been unmounted from the DOM, this is an indication that there could be a memory leak, but it is an indication with a lot of false positives.
How do I know if I have a memory leak?
You have a memory leak if an object that lives longer than your component holds a reference to it, either directly or indirectly. This usually happens when you subscribe to events or changes of some kind without unsubscribing when your component unmounts from the DOM.
It typically looks like this:
useEffect(() => {
function handleChange() {
setState(store.getState())
}
// "store" lives longer than the component,
// and will hold a reference to the handleChange function.
// Preventing the component to be garbage collected after
// unmount.
store.subscribe(handleChange)
// Uncomment the line below to avoid memory leak in your component
// return () => store.unsubscribe(handleChange)
}, [])
Where store is an object that lives further up the React tree (possibly in a context provider), or in global/module scope. Another example is subscribing to events:
useEffect(() => {
function handleScroll() {
setState(window.scrollY)
}
// document is an object in global scope, and will hold a reference
// to the handleScroll function, preventing garbage collection
document.addEventListener('scroll', handleScroll)
// Uncomment the line below to avoid memory leak in your component
// return () => document.removeEventListener(handleScroll)
}, [])
Another example worth remembering is the web API setInterval, which can also cause memory leak if you forget to call clearInterval when unmounting.
But that is not what I am doing, why should I care about this warning?
React's strategy to warn whenever state updates happen after your component has unmounted creates a lot of false positives. The most common I've seen is by setting state after an asynchronous network request:
async function handleSubmit() {
setPending(true)
await post('/someapi') // component might unmount while we're waiting
setPending(false)
}
You could technically argue that this also is a memory leak, since the component isn't released immediately after it is no longer needed. If your "post" takes a long time to complete, then it will take a long time to for the memory to be released. However, this is not something you should worry about, because it will be garbage collected eventually. In these cases, you could simply ignore the warning.
But it is so annoying to see the warning, how do I remove it?
There are a lot of blogs and answers on stackoverflow suggesting to keep track of the mounted state of your component and wrap your state updates in an if-statement:
let isMountedRef = useRef(false)
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
}, [])
async function handleSubmit() {
setPending(true)
await post('/someapi')
if (!isMountedRef.current) {
setPending(false)
}
}
This is not an recommended approach! Not only does it make the code less readable and adds runtime overhead, but it might also might not work well with future features of React. It also does nothing at all about the "memory leak", the component will still live just as long as without that extra code.
The recommended way to deal with this is to either cancel the asynchronous function (with for instance the AbortController API), or to ignore it.
In fact, React dev team recognises the fact that avoiding false positives is too difficult, and has removed the warning in v18 of React.
You can try this set a state like this and check if your component mounted or not. This way you are sure that if your component is unmounted you are not trying to fetch something.
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
return () => setDidMount(false);
}, [])
if(!didMount) {
return null;
}
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
Hope this will help you.
I had a similar issue with a scroll to top and #CalosVallejo answer solved it :) Thank you so much!!
const ScrollToTop = () => {
const [showScroll, setShowScroll] = useState();
//------------------ solution
useEffect(() => {
checkScrollTop();
return () => {
setShowScroll({}); // This worked for me
};
}, []);
//----------------- solution
const checkScrollTop = () => {
setShowScroll(true);
};
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
window.addEventListener("scroll", checkScrollTop);
return (
<React.Fragment>
<div className="back-to-top">
<h1
className="scrollTop"
onClick={scrollTop}
style={{ display: showScroll }}
>
{" "}
Back to top <span>⟶ </span>
</h1>
</div>
</React.Fragment>
);
};
I have getting same warning, This solution Worked for me ->
useEffect(() => {
const unsubscribe = fetchData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
if you have more then one fetch function then
const getData = () => {
fetch1();
fetch2();
fetch3();
}
useEffect(() => {
const unsubscribe = getData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
This error occurs when u perform state update on current component after navigating to other component:
for example
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
dispatch(login(res.data.data)); // line#5 logging user in
setSigningIn(false); // line#6 updating some state
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
In above case on line#5 I'm dispatching login action which in return navigates user to the dashboard and hence login screen now gets unmounted.
Now when React Native reaches as line#6 and see there is state being updated, it yells out loud that how do I do this, the login component is there no more.
Solution:
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
setSigningIn(false); // line#6 updating some state -- moved this line up
dispatch(login(res.data.data)); // line#5 logging user in
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
Just move react state update above, move line 6 up the line 5.
Now state is being updated before navigating the user away. WIN WIN
there are many answers but I thought I could demonstrate more simply how the abort works (at least how it fixed the issue for me):
useEffect(() => {
// get abortion variables
let abortController = new AbortController();
let aborted = abortController.signal.aborted; // true || false
async function fetchResults() {
let response = await fetch(`[WEBSITE LINK]`);
let data = await response.json();
aborted = abortController.signal.aborted; // before 'if' statement check again if aborted
if (aborted === false) {
// All your 'set states' inside this kind of 'if' statement
setState(data);
}
}
fetchResults();
return () => {
abortController.abort();
};
}, [])
Other Methods:
https://medium.com/wesionary-team/how-to-fix-memory-leak-issue-in-react-js-using-hook-a5ecbf9becf8
If the user navigates away, or something else causes the component to get destroyed before the async call comes back and tries to setState on it, it will cause the error. It's generally harmless if it is, indeed, a late-finish async call. There's a couple of ways to silence the error.
If you're implementing a hook like useAsync you can declare your useStates with let instead of const, and, in the destructor returned by useEffect, set the setState function(s) to a no-op function.
export function useAsync<T, F extends IUseAsyncGettor<T>>(gettor: F, ...rest: Parameters<F>): IUseAsync<T> {
let [parameters, setParameters] = useState(rest);
if (parameters !== rest && parameters.some((_, i) => parameters[i] !== rest[i]))
setParameters(rest);
const refresh: () => void = useCallback(() => {
const promise: Promise<T | void> = gettor
.apply(null, parameters)
.then(value => setTuple([value, { isLoading: false, promise, refresh, error: undefined }]))
.catch(error => setTuple([undefined, { isLoading: false, promise, refresh, error }]));
setTuple([undefined, { isLoading: true, promise, refresh, error: undefined }]);
return promise;
}, [gettor, parameters]);
useEffect(() => {
refresh();
// and for when async finishes after user navs away //////////
return () => { setTuple = setParameters = (() => undefined) }
}, [refresh]);
let [tuple, setTuple] = useState<IUseAsync<T>>([undefined, { isLoading: true, refresh, promise: Promise.resolve() }]);
return tuple;
}
That won't work well in a component, though. There, you can wrap useState in a function which tracks mounted/unmounted, and wraps the returned setState function with the if-check.
export const MyComponent = () => {
const [numPendingPromises, setNumPendingPromises] = useUnlessUnmounted(useState(0));
// ..etc.
// imported from elsewhere ////
export function useUnlessUnmounted<T>(useStateTuple: [val: T, setVal: Dispatch<SetStateAction<T>>]): [T, Dispatch<SetStateAction<T>>] {
const [val, setVal] = useStateTuple;
const [isMounted, setIsMounted] = useState(true);
useEffect(() => () => setIsMounted(false), []);
return [val, newVal => (isMounted ? setVal(newVal) : () => void 0)];
}
You could then create a useStateAsync hook to streamline a bit.
export function useStateAsync<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
return useUnlessUnmounted(useState(initialState));
}
Try to add the dependencies in useEffect:
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [fetchData, props.spotifyAPI])
Usually this problem occurs when you showing the component conditionally, for example:
showModal && <Modal onClose={toggleModal}/>
You can try to do some little tricks in the Modal onClose function, like
setTimeout(onClose, 0)
This works for me :')
const [state, setState] = useState({});
useEffect( async ()=>{
let data= await props.data; // data from API too
setState(users);
},[props.data]);
I had this problem in React Native iOS and fixed it by moving my setState call into a catch. See below:
Bad code (caused the error):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
}
setLoading(false) // this line was OUTSIDE the catch call and triggered an error!
}
Good code (no error):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
setLoading(false) // moving this line INTO the catch call resolved the error!
}
}
Similar problem with my app, I use a useEffect to fetch some data, and then update a state with that:
useEffect(() => {
const fetchUser = async() => {
const {
data: {
queryUser
},
} = await authFetch.get(`/auth/getUser?userId=${createdBy}`);
setBlogUser(queryUser);
};
fetchUser();
return () => {
setBlogUser(null);
};
}, [_id]);
This improves upon Carlos Vallejo's answer.
useEffect(() => {
let abortController = new AbortController();
// your async action is here
return () => {
abortController.abort();
}
}, []);
in the above code, I've used AbortController to unsubscribe the effect. When the a sync action is completed, then I abort the controller and unsubscribe the effect.
it work for me ....
The easy way
let fetchingFunction= async()=>{
// fetching
}
React.useEffect(() => {
fetchingFunction();
return () => {
fetchingFunction= null
}
}, [])
options={{
filterType: "checkbox"
,
textLabels: {
body: {
noMatch: isLoading ?
:
'Sorry, there is no matching data to display',
},
},
}}

Array is Updating with useEffect but Always Get the Initial Value

This question has been asked many times but none of the solutions have worked for me. I miss something and I need your help to find it out.
I also provide the whole code to give you context.
I have a TV app that shows the production of a factory.
Initially I request the server to check if statically defined lines are working right now.
let lineIds = ["3", "1", "9"]
TvApp.getInitialProps = async (ctx) => {
try {
const res = await axios.get(`http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}`)
const workingLinesResponse = await res
console.log('workingLinesResponse: data: ', workingLinesResponse.data)
return { workingLines: workingLinesResponse.data }
} catch (error) {
console.log('error getInitialProps:: ', error)
return { data: null }
}
}
In the first useEffect, I keep checking every 15 seconds if the lines are still working;
Server responds with the workingLines.
In the second useEffect, I use workingLines to get production data for the currently working lines.
My problem is;
In the first useEffect, setWorkingLines(response.data) is updating workingLines but cannot be used by the second useEffect. Second useEffect always shows the initial value.
export default function TvApp(props) {
const [workingLines, setWorkingLines] = useState(props.workingLines);
//first useEffect
useEffect(() => {
const timer = setInterval(() => {
const API = `http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}`
axios.get(API)
.then(response => {
console.log('workingLines initially:: ', response.data)
setWorkingLines(response.data)
})
.catch(error => {
console.log(error.message);
});
return () => clearInterval(timer);
}, 15000);
},[])
var activeLineId = 0
//second useEffect
useEffect(() => {
const timer = setInterval(() => {
axios.get(`http://192.168.1.10:8090/api/tv_app/:${workingLines[activeLineId]}`)//workingLines is always the initial value, not updated one.
.then(response => {
//Set reponse to use in the view.
})
if (activeLineId === workingLines.length - 1) {
activeLineId = 0
} else {
activeLineId = activeLineId + 1
}
return () => clearInterval(timer);
}, 10000);
}, []); //using workingLines as a dependency is causing the multiple api requests
}
If I use workingLines as a dependency in the second useEffect, the api request is calling multiple times.
I can check the workingLines with following useEffect:
useEffect(() => {
console.log('workingLines:: ', workingLines) //this shows that, workingLines is successfully updated by the first useEffect
}, [workingLines])
How can I use the updated workingLines in the second useEffect without calling the api multiple times?

How can I use an effect once, but still use state, with React Hooks?

I am trying to set an interval on mount/unmount only, but allow the callback to have one variable from state.
I have code like this:
const [cachedData, setCachedData] = useState(false);
async function refreshData() {
const data = await axios('http://www.example.com/');
setCachedData(data);
}
useEffect(() => {
let interval;
async function fetch() {
await refreshData();
interval = setInterval(refreshData,5000);
console.log('set interval to', interval)
}
fetch();
return () => {
console.log('clearing interval', interval);
clearInterval(interval);
}
}, []);
I am running into a catch-22. The second argument of useEffect says what variables to pay attention to. I've read making it an empty array makes this only mount/unmount rather than on any state updates. The issue I've found is that doing that, means refreshData has no access to cachedData, so I can't know I have valid data (to avoid the XHR request for an amount of time). If I pass cachedData into second argument of useEffect, it will have the variable but run more than it should. Not sure of a way around this.
I should note that if I pass cachedData to the second arg, and console.log by the clear and the setting of the interval, my console outputs something like this:
clearing interval undefined
set interval to 5
set interval to 7
So it seemingly runs the unmount and then the effect twice over without clearing again. This causes a double axios call.
You can use a ref to get at the current cachedData with something like the following:
const [cachedData, setCachedData] = useState(false);
const cachedDataRef = useRef(cachedData);
useEffect(() => {
// Update ref in effect so that render has no side effects.
cachedDataRef.current = cachedData;
}, [cachedData]);
useEffect(() => {
async function refreshData() {
if (cachedDataRef.current) {
// do something different
} else {
const data = await axios('http://www.example.com/');
setCachedData(data);
}
}
let interval;
async function fetch() {
await refreshData();
interval = setInterval(refreshData,5000);
console.log('set interval to', interval)
}
fetch();
return () => {
console.log('clearing interval', interval);
clearInterval(interval);
}
}, []);

Resources