setInterval keeps running even after clearInterval - javascript

Thanks in advance.
This is my question : It is Quiz website for couple.
Before partner finish quiz, It send get request to server every 5s.
but the problem is even partner's answers are set, setInterval never stops.
but if I refresh my website, It works well.
Can you please give me advise?
const postAnswers = useGetResults();
const postPartnerAnswers = useGetPartnerResults();
const [myResult, setMyResult] = useState<FinalAnswer | undefined>();
const [partnerResult, setPartnerResult] = useState<FinalAnswer | undefined>();
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(false);
const init = async () => {
try {
const email = localStorage.getItem('email');
const partnerEmail = localStorage.getItem('partnerEmail');
if (email !== undefined && partnerEmail !== undefined) {
// localStorage에 이메일 값들이 있으면,
const result = await postAnswers(email, partnerEmail);
const otherResult = await postPartnerAnswers(email, partnerEmail);
if (result.answers !== undefined && otherResult.answers !== undefined) {
// 몽고디비에서 받아온 값이 둘다 있으면
setMyResult(result);
setPartnerResult(otherResult);
} else {
// 몽고디비에서 받아온 값이 없으면
console.log(result.answers, otherResult.answers);
setIsLoading(true);
}
}
} catch (error) {
setErrorMessage('로딩하는 도중 에러가 발생했습니다');
console.error(error);
}
};
useEffect(() => {
init();
}, []);
useEffect(() => {
if (myResult !== undefined && partnerResult !== undefined) {
setIsLoading(false);
console.log('둘다 값이 있어요!');
console.log(isLoading);
}
}, [myResult, partnerResult]);
const timer = () => {
return setInterval(() => {
init();
console.log('isLoading', isLoading);
if (isLoading === false) {
console.log('clear');
clearInterval(timer());
}
}, 5000);
};
useEffect(() => {
if (isLoading === true) {
console.log('둘다 값이 없어요!');
timer();
}
if (isLoading === false) {
console.log('clear');
clearInterval(timer());
}
}, [isLoading]);
deployed website : https://www.couple-quiz.com/

Expanding on #Ethansocal comment:
Your code is calling clearInterval(timer()) which will create a new interval that it will immediately clear. It seems that you are confusing the API of removeEventListener and clearInterval.
clearInterval should be called with the identifier returned by setInterval.
I suggest getting rid of the timer function and rewriting your last useEffect to make it return a cleanup function when isLoading is true:
useEffect(() => {
if (isLoading) {
console.log('둘다 값이 없어요!');
const interval = setInterval(init, 5_000);
return () => { clearInterval(interval) };
} else {
console.log('clear');
}
}, [isLoading]);

Related

React Native Socket Slow Performance

I have a markets screen where I am using sockets to update the prices of cryptocurrencies in real time. The screen contains an infinite scroller, so when the user scrolls, more cryptocurrencies load and the coins being observed by the socket changes as well. However I am noticing as the coins list is increasing, the app becomes really slow and I cannot navigate to other screens or click anywhere quickly.
I have seen a few apps achieve this infinite-scroll-live-prices logic such as CoinGecko & CoinMarketCap.
Snippet of the relevant code:
const updatePriceOfCoins = (newPrices = {}, coins = []) => {
const updatedCoins = [...coins];
let wasUpdated = false;
for (let i = 0; i < updatedCoins.length; i++) {
let coin = updatedCoins[i];
if (newPrices[coin.id] !== undefined) {
updatedCoins[i] = { ...coin, priceUsd: newPrices[coin.id] };
wasUpdated = true;
}
}
return { wasUpdated, coins: updatedCoins };
};
const MarketsScreen = ({
markets,
getMarkets,
isLoading,
isLoadingMore,
perPage,
getMoreMarkets,
hasMore,
updateMarkets
}) => {
const socket = useLivePrices(markets);
const marketsRef = useRef(markets);
useEffect(() => {
marketsRef.current = markets;
}, [markets]);
const onNewPrices = (newPrices) => {
const { wasUpdated, coins: updatedMarkets } = updatePriceOfCoins(newPrices, marketsRef.current);
if (wasUpdated) {
updateMarkets(updatedMarkets);
}
};
useEffect(() => {
getMarkets();
}, []);
useEffect(() => {
if (socket !== null) {
socket.on("new prices", onNewPrices);
}
return () => {
if (socket !== null) {
socket.off("new prices");
}
};
}, [socket]);
return (
<FlatList
data={data}
renderItem={renderDataItem}
showsVerticalScrollIndicator={false}
onEndReached={getMoreMarkets}
onEndReachedThreshold={0.5}
/>
);
};
useLivePrices hook
const useLivePrices = (coinsToWatch = []) => {
const [socket, setSocket] = useState(null);
const prevCommaSepCoins = useRef("");
useEffect(() => {
//Only initialize socket once then everytime coinsToWatch is different
//update the coins observed
if (coinsToWatch.length > 0) {
if (socket === null) {
const commaSepCoins = coinsToCommaSepIDs(coinsToWatch);
setSocket(connectToLivePricesSocket(commaSepCoins));
prevCommaSepCoins.current = commaSepCoins;
} else {
const newCommaSepCoins = coinsToCommaSepIDs(coinsToWatch);
if (prevCommaSepCoins.current !== newCommaSepCoins) {
socket.emit("update coins", newCommaSepCoins);
prevCommaSepCoins.current = newCommaSepCoins;
}
}
}
}, [coinsToWatch]);
useEffect(() => {
let unsubFocus = () => {};
let unsubBlur = () => {};
if (socket !== null) {
//pause and resume prices based on if screen is in focus
unsubFocus = navigation.addListener("focus", resumePrices);
unsubBlur = navigation.addListener("blur", pausePrices);
}
return () => {
if (socket !== null) {
socket.disconnect();
unsubFocus();
unsubBlur();
}
};
}, [socket]);
return socket;
};
I want to achieve the infinite-scroll-live-prices but not sure how to optimize the performance anymore.
I tried optimizing the performance by reducing the number of renders when price updates. I have also tried to pause and resume the socket based on if the screen is focused so that state updates are not happening while the screen is not focused.

useEffect with firestore

I'm trying to do the following, i cannot get any errors but what's weird is, while setRivalGuess in the first condition setRivalGuess(doc.data().guess2) doesn't work, the second one setRivalGuess(doc.data().guess1) works really well. I checked database and everything stored well, that is, each data that I want to fetch is available on the database. I don't know whether it is about my way of using useEffect.
const { rivalGuess, setRivalGuess } = useGame();
const game = query(roomColRef, where("roomId", "==", roomId))
useEffect(() => {
const getUsers = async () => {
const data = await getDocs(game);
data.forEach((doc)=> {
if (doc.data().numberOfPlayers == 2 ){
if(userValue == doc.data().players[0].username)
if (doc.data().guess2 =! 0){
setRivalGuess(doc.data().guess2)}
if (userValue == doc.data().players[1].username)
if (doc.data().guess1 =! 0){
setRivalGuess(doc.data().guess1)} }})};
getUsers();
}, [ rivalGuess, setRivalGuess ])
rivalGuess was before global state , but know it's in the hook.
const UseRivals = (collectionStr) =>{
const [ rivalGuess, setRivalGuess ] =useState([])
const { roomId, userValue } = useGame()
useEffect(() => {
const collectionRef = collection(db, collectionStr);
const q = query(collectionRef, where("roomId", "==", roomId ))
const unSub = onSnapshot(q , (snapshot) => {
snapshot.docs.forEach(doc => {
if (doc.data().numberOfPlayers==2) {
if (userValue == doc.data().players[0].username) if (doc.data().guess2 =! 0)
{ setRivalGuess(doc.data().guess2) }
if (userValue == doc.data().players[1].username) if (doc.data().guess1 =! 0)
{ setRivalGuess(doc.data().guess1)}}})
}, (err) => {
console.log(err.message);
});
return () => unSub();
}, [collectionStr]);
return { rivalGuess };
}
export default UseRivals;

Multiple axios get request not returning the data properly

I have created a react hook to work on with multiple get request using axios
const useAxiosGetMultiple = (urls,{preventCall = false} = {}) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState(()=>{
const temp = {}
Object.keys(urls).forEach(key => temp[key] = [])
return temp
})
const [reloadToken, setReloadToken] = useState(false)
const urlObj = useRef({...urls})
const unmountedOnReload = useRef(false)
useEffect(() => {
if(preventCall === true){
return null
}
let unmounted = false;
const source = axios.CancelToken.source();
setLoading(true)
const requests = []
Object.values(urlObj.current).forEach(url => {
requests.push(
axios.get(url, {
cancelToken: source.token,
})
);
});
const result = {}
const errors = {}
console.log(requests)
Promise.allSettled(requests)
.then(resArray => {
if(!unmounted){
console.log('from promise allsettled')
console.log(resArray)
console.log(urlObj.current)
Object.keys(urlObj.current).forEach((key,i) =>{
if(resArray[i].status === 'fulfilled'){
result[key] = resArray[i].value.data.responseData
}
if(resArray[i].status === 'rejected'){
errors[key] = resArray[i].reason
result[key] = []
}
})
setError(errors)
setLoading(false)
setResponse(result)
}
})
.catch(err => {
if (!unmounted) {
setError(err);
setLoading(false);
setResponse([])
if (axios.isCancel(err)) {
console.log(`request cancelled:${err.message}`);
} else {
console.log("another error happened:" + err.message);
}
}
})
return () => {
unmounted = true;
unmountedOnReload.current = true
source.cancel("Api call cancelled on unmount");
};
}, [reloadToken,preventCall]);
const reFetchAll = () => {
setReloadToken((token) => !token);
};
const reload = (urlKey) =>{
unmountedOnReload.current = false
setLoading(true)
axios.get(urls[urlKey])
.then(res =>{
if(!unmountedOnReload.current){
setLoading(false)
setResponse({...response,[urlKey]: res.data.responseData})
}
})
.catch(err=>{
if(!unmountedOnReload.current){
setLoading(false)
setError({...error, [urlKey]: err})
setResponse({...response,[urlKey]: []})
}
})
}
return {response, loading, error, reFetchAll, reload, setLoading};
};
I call this hook as follows..
const {response,loading,setLoading,reload} = useAxiosGetMultiple({
stateCodes: StateCode.api,
countryCodes: CountryCode.api,
districts: District.api,
})
Rather than getting variable stateCodes containing state codes or countryCodes containing country codes it's returning in wrong order or returning same data in multiple variable. Every time the call happens every time it changes. I also tried axios.all method instead of Promise.all but problem remains same.
Even in chrome's network panel the response data is improper.
What's the possible cause for this error and how to fix it ?
Thanks in advance

Rendered fewer hooks than expected. This may be caused by an accidental early return statement

I'm getting this error when triggering a setState inside of a custom React hook. I'm not sure of how to fix it, can anyone show me what I'm doing wrong. It is getting the error when it hits handleSetReportState() line. How should I be setting the report state from inside the hook?
custom useinterval poll hook
export function usePoll(callback: IntervalFunction, delay: number) {
const savedCallback = useRef<IntervalFunction | null>()
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
function tick() {
if (savedCallback.current !== null) {
savedCallback.current()
}
}
const id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])
}
React FC
const BankLink: React.FC = ({ report: _report }) => {
const [report, setReport] = React.useState(_report)
if ([...Statues].includes(report.status)) {
usePoll(async () => {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}, 5000)
}
... rest
This is because you put usePoll in if condition, see https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
You can put the condition into the callback
usePoll(async () => {
if ([...Statues].includes(report.status)) {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}
}, 5000)
And if the delay will affect report.status, use ref to store report.status and read from ref value in the callback.

jump to specific time in videojs using react hooks

I am trying to implement feature to jump 15 seconds forward or backward in video.
I am facing hard time to set the update and set the current time.
const videoNode = useRef(null);
const [currentTime, setCurrentTime] = useState(null);
const handleTimeJump = (type) => {
const player = videojs(videoNode.current);
console.log(player)
if (player) {
type === 'inc' && setCurrentTime(player.currentTime() + 15);
player.currentTime() > 15 ? setCurrentTime(player.currentTime() - 15) : setCurrentTime(0)
}
};
useEffect(() => {
const player = videojs(
videoNode.current,
videoJsOptions,
function onPlayerReady() {
console.log('onPlayerReady');
player.on('timeupdate', () => {
setCurrentTime(player.currentTime());
});
},
);
if (!videoJsOptions.sources[0].src) {
console.log('no source found');
}
return () => {
if (player) {
player.dispose();
}
};
}, []);
useEffect(() => {
const player = videojs(videoNode.current)
player.currentTime(currentTime)
}, [currentTime])
handleTimeJump is called after clicking a button.
onClick={() => handleTimeJump('inc')}
Look I haven't tested if it works but looks like it should be player.setCurrentTime(currentTime) instead of player.currentTime(currentTime)
If that works then they should have thrown an error when calling currentTime with an argument because it is not supposed to take an argument (*cough* or you could use a statically typed language *cough*)
Also the currentTime state is already in the videojs-land there's no need to create another in React-land and keep them in sync. You're dispatching a react update EVERY SECOND. Here's a higly recommended and unsolicited refactor (keeping diff as less as possible):
const videoNode = useRef(null);
const playerRef = useRef(null);
const player = playerRef.current;
const handleTimeJump = (type) => {
if (player) {
type === 'inc' && player.setCurrentTime(player.currentTime() + 15);
player.currentTime() > 15 ? player.setCurrentTime(player.currentTime() - 15) : player.setCurrentTime(0)
}
};
useEffect(() => {
playerRef.current = videojs(
videoNode.current,
videoJsOptions
);
if (!videoJsOptions.sources[0].src) {
console.log('no source found');
}
return () => {
if (player) {
player.dispose();
}
};
}, []);

Categories