React Hooks: Timeout occurred during unit testing waitForValueToChange - javascript

export function useTimer(initValue: number, isProceed: boolean) {
const [value, setter] = useState(initValue)
useEffect(() => {
setter(initValue)
const interval = setInterval(() => {
if (isProceed && value > 0) setter((c) => c - 1)
else if (value < 0) {
clearInterval(interval)
}
}, 1000)
return () => {
clearInterval(interval)
}
}, [initValue, value, isProceed])
return {
seconds:
value > 0
? `${Math.floor(value / 60)}:${value % 60 < 10 ? '0' : ''}${value % 60}`
: '',
resetTimer: () => setter(initValue),
}
}
test('InitialCountState < 0', async () => {
const { hydrate, result, waitForValueToChange } = renderHook(() =>
useTimer(InitialCountState, isStartTimer)
)
InitialCountState = -1
isStartTimer = true
hydrate()
await waitForValueToChange(() => result.current.seconds)
expect(result.current.seconds).toBe('')
})
Timed out in waitForValueToChange after 1000ms.
Failed to resolve failure result during hooks test. It's said that it failed because it was over time, but I don't know how to solve it. Is there a good way?

Related

setInterval keeps running even after clearInterval

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]);

RTK query useQuery invalidateTags on infinity scroll

I have this function that basically helps me implement infinite scroll everywhere. Still, I faced a problem where when I invalidate a tag related to an endless scroll tag it doesn't update the needed portion because of the offset and limit parameters.
The way I provide tags:
providesTags: (item) => item?.result
? [...item.result.departures.map(({ ID }) => ({
type: 'Departures',
id: ID,
})),
{ type: 'Departures', id: 'LIST' },
]
: [{ type: 'TransitDepartures', id: 'LIST' }],
To Invalidate tags I use invalidatesTags
The function I described
export const isValidNotEmptyArray = (array) =>
!!(array && array?.length && array?.length > 0)
const useFetchQuery = (
useGetDataListQuery,
{ offset = 0, limit = 10, ...queryParameters },
filter = () => true,
) => {
const [localOffset, setLocalOffset] = useState(offset)
const [combinedData, setCombinedData] = useState([])
const [gotWiped, setGotWiped] = useState(0)
const queryResponse = useGetDataListQuery(
{
offset: localOffset,
limit,
...queryParameters,
},
)
const { data: fetchData = { result: [], total: 0 } } = queryResponse || {}
const total = useMemo(() => fetchData.total, [fetchData])
useEffect(() => {
const value = departure ? fetchData.result.departures : fetchData.result
if (isValidNotEmptyArray(value)) {
setGotWiped(0)
if (localOffset === 0 || !localOffset) {
setCombinedData(value)
} else {
setCombinedData((previousData) => [...previousData, ...value])
}
} else if (gotWiped === 0) {
setGotWiped(1)
}
}, [fetchData])
useEffect(() => {
if (gotWiped) {
setCombinedData([])
}
}, [gotWiped])
const refresh = () => {
setLocalOffset((prev) => (prev === 0 ? null : 0))
setCombinedData([])
}
const loadMore = () => {
if (combinedData.length < total) {
setLocalOffset(combinedData.length)
}
}
return {
data: useMemo(() => combinedData.filter(filter), [combinedData, filter]),
offset: localOffset,
total:
combinedData.length > combinedData.filter(filter).length
? combinedData.filter(filter).length
: total,
loadMore,
refresh,
isLoading: queryResponse?.isLoading,
isFetching: queryResponse?.isFetching,
}
}

Why this setInterval is executing multiple times?

I have the below code in a vue application
mounted: function () {
this.timer = setInterval(async () => {
if (this.progress >= 1) {
this.progress = 1
clearInterval(this.timer)
}
console.log('update')
const id = this.$route.params.id
const progOut = await this.api.get(`/api/mu/job/${id}/status`)
const response = progOut.data
this.progress = response.data.progress / 100
this.state = response.data.status
}, 7000)
},
I was expecting it to execute the get request every 7 seconds but it is executing the call every 500ms approx
I read other answers and so far I think this is the proper way but the code is executing too many requests
What is the proper way to call a function from within the setInterval to make it actually wait the timeout?
Edit: This was my final code in case someone goes through the same
methods: {
redirect (page) {
if (page === 'FINISHED') {
this.$router.push({
name: 'viewReport',
params: { id: 4 }
})
} else {
this.$router.push({
name: 'errorOnReport',
params: { id: 13 }
})
}
}
},
watch: {
state: async function (newVal, old) {
console.log('old ' + old + ' newVal ' + newVal)
if (newVal === 'FAILED' || newVal === 'FINISHED') {
this.redirect(newVal)
}
}
},
data () {
return {
state: null,
timer: null,
progress: 0.0,
progressStr: '0%'
}
},
mounted () {
const update = async () => {
if (this.progress >= 1) {
this.progress = 1
}
console.log('update ' + new Date())
const id = this.$route.params.id
const progOut = await this.api.get(`/api/mu/job/${id}/status`)
const response = progOut.data
this.state = response.data.status
this.progress = response.data.progress / 100
this.progressStr = response.data.progress + '%'
}
update()
this.timer = setInterval(update, 10000)
},
beforeUnmount () {
clearInterval(this.timer)
}
A better design is to wrap setTimeout with a promise, and do the polling in an async method that loops...
mounted: function() {
this.continuePolling = true; // suggestion: we have to stop sometime. consider adding continuePolling to data
this.poll();
},
unmounted: function() { // almost the latest possible stop
this.continuePolling = false;
},
methods:
async poll(interval) {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
while(this.continuePolling) {
await this.updateProgress();
await delay(7000);
}
},
async updateProgress() {
const id = this.$route.params.id
const progOut = await this.api.get(`/api/mu/job/${id}/status`)
const result = progOut.data.data;
this.progress = result.progress / 100
this.state = result.status
}

How cancel Timeout inside object

I have the following code:
const timers = []
const timer1 = setTimeout(() => {
console.log('Starting timer2')
const timer2 = setTimeout(() => {
console.log('Its works')
}, 10000)
timers.push({key:2, id:timer2})
}, 10000);
timers.push({key:1, id:timer1})
function remove(key) {
for (i = 0; i > timers.length; i++) {
if (timers[i].key === key) {
timers = timers.slice(i, 1)
clearTimeout(timers[i].id)
}
}
}
When I call the remove(key) function the code is not removing the timers as expected
const timers = []
const timer1 = setTimeout(() => {
console.log('Starting timer2')
const timer2 = setTimeout(() => {
console.log('Its works')
}, 10000)
timers.push({key:2, id:timer2})
}, 10000);
timers.push({key:1, id:timer1})
function remove(key) {
const timer = timers.find(f => f.key === key);
if (timer) {
clearTimeout(timer.id);
}
}

add condition with variable inside const IntersectionObserver

I have following hook that is added on start:
const scrollObserver = useCallback(
(node) => {
new IntersectionObserver((entries) => {
entries.forEach((en) => {
if (en.intersectionRatio > 0.5) {
pagerDispatch({ type: 'ADVANCE_PAGE' }
}
})
}).observe(node)
},
[pagerDispatch]
)
useEffect(
() => {
if (bottomBoundaryRef.current) {
scrollObserver(bottomBoundaryRef.current)
}
},
[scrollObserver, bottomBoundaryRef]
)
The problem I'm facing that I want to add if condition with a variable value that updates over time. If I add following code then data.length is always 0. I guess that's because it has in store the state that was on start.
if (en.intersectionRatio > 0.5) {
if (data.length <= iScrollMax) {
pagerDispatch({ type: 'ADVANCE_PAGE' })
}
}
How do I add condition with updatable variable here?
ps. I've tried making separate fuction but that did not work too.
example
function upd() {
if (data.length <= iScrollMax) {
pagerDispatch({ type: 'ADVANCE_PAGE' })
}
}
const scrollObserver = useCallback(
(node) => {
new IntersectionObserver((entries) => {
entries.forEach((en) => {
if (en.intersectionRatio > 0.5) {
upd()
}
})
}).observe(node)
},
[pagerDispatch]
)
You aren't seeing the updated data value in the function because of closure. Your function is only recreated on change on pagerDispatch and so when the data values update, it isn't made aware of it and keeps using the old data value it had when it was created
The solution is to add data as dependency to useCallback and also ensure you cleanup your observer in useEffect
const scrollObserver = useCallback(
(node) => {
return new IntersectionObserver((entries) => {
entries.forEach((en) => {
if (en.intersectionRatio > 0.5) {
if (data.length <= iScrollMax) {
pagerDispatch({ type: 'ADVANCE_PAGE' }
}
}
})
}).observe(node)
},
[pagerDispatch, data]
)
useEffect(
() => {
let observer;
if (bottomBoundaryRef.current) {
observer=scrollObserver(bottomBoundaryRef.current)
}
return () => {
observer && observer.disconnect();
}
},
[scrollObserver, bottomBoundaryRef]
)
Approach 2: There is a getaway to this via a ref
const dataRef = useRef(data);
useEffect(() => {
dataRef.current = data;
}, [data]);
const scrollObserver = useCallback(
(node) => {
return new IntersectionObserver((entries) => {
entries.forEach((en) => {
if (en.intersectionRatio > 0.5) {
if (dataRef.current.length <= iScrollMax) {
pagerDispatch({ type: 'ADVANCE_PAGE' }
}
}
})
}).observe(node)
},
[pagerDispatch]
)
useEffect(
() => {
let observer;
if (bottomBoundaryRef.current) {
observer=scrollObserver(bottomBoundaryRef.current)
}
return () => {
observer && observer.disconnect();
}
},
[scrollObserver, bottomBoundaryRef]
)

Categories