Hi, thanks for sharing, and I'm glad it's useful to you! Full disclosure, this is a cleanup of the first custom hook I ever wrote around 2 years ago. I glossed over that part in my cleanup because I assumed it was right, but I wrote it before I fully understood hooks (even now, with scenarios like this, they're kinda tricky).
Looking closer, it seems like the clearTimeout is completely redundant with the cleanup function - which is always called whenever the threshold changes, or when the component unmounts. I've removed it which should help with the confusion. Thanks for flagging, I wouldn't have found this redundancy otherwise.
For the custom intervals, I don't think it would be hard to implement, you'd just need to change some of the math with startOfThreshold and msUntilNext. Making it fully featured in that sense, you could even add an offset to account for something like "the 3rd minute of every hour".
I think it would also be some extra work to anchor the 15 minutes against the hour, rather than any 15 minutes - though it could also be another hard coded threshold.
At some point I might write about testing this - since timeouts and intervals can be tricky to test, and there's a few interesting edge cases.
I have extended your hook, to support amounts of thresholds (before it was just 1), converted the hook to TypeScript and remove the extra cleanup as you suggested:
Then I wanted to add some tests, but I can't get it to run properly, let alone pass green.
import{act,renderHook,cleanup}from"@testing-library/react-hooks"import{jest}from"@jest/globals"importuseDateTimefrom"./useDateTime"describe("useDateTime hook",()=>{beforeAll(()=>{jest.useFakeTimers("modern")})afterEach(cleanup)afterAll(()=>{jest.useRealTimers()})it("should report time updates every 30 minutes",async()=>{jest.setSystemTime(newDate("2020-11-19T10:00:00.000Z"))const{result}=renderHook(()=>useDateTime("minute"))act(()=>{jest.advanceTimersByTime(1000*1*60)//jest.runOnlyPendingTimers()})expect(result.current.toISOString()).toEqual("2020-11-19T10:01:00.000Z")})})
OK, I have the tests working now and they instantly revealed some bugs in the hook. Getting tests to run properly (for a react-native project if that matters) was the main challenge.
It involved a custom jest-preset to work around some promise related bug:
// Because fake timers did not work// See:// https://github.com/facebook/jest/issues/10221#issuecomment-730305710// https://github.com/sbalay/without_await/commit/64a76486f31bdc41f5c240d28263285683755938constreactNativePreset=require("react-native/jest-preset")module.exports=Object.assign({},reactNativePreset,{setupFiles:[require.resolve("./save-promise.js")].concat(reactNativePreset.setupFiles).concat([require.resolve("./restore-promise.js")]),})
This test now runs green:
import{act,renderHook,cleanup}from"@testing-library/react-hooks"import{jest}from"@jest/globals"importuseDateTimefrom"./useDateTime"describe("useDateTime hook",()=>{beforeAll(()=>{jest.useFakeTimers("modern")})afterEach(cleanup)afterAll(()=>{jest.useRealTimers()})it("should report time updates every 30 minutes",()=>{jest.setSystemTime(newDate("2020-11-19T10:00:00.000Z"))const{result}=renderHook(()=>useDateTime("minute",30))expect(result.current.toISOString()).toEqual("2020-11-19T10:00:00.000Z")act(()=>jest.advanceTimersByTime(1000*30*60))expect(result.current.toISOString()).toEqual("2020-11-19T10:30:00.000Z")})})
A bug it revealed was that the hook reported the estimated future date before the timeout actually happened. In your component this was not a problem, you did not know you were one second ahead of system time ;) That happened because it called setDate outside the timeout callback.
I’ll take a look, but this change likely breaks its ability to immediately respond to threshold changes e.g. second to minute.
I also wouldn’t expect the setDate to change unless the time has actually passed by more than a second since it goes to the start of the threshold.
Again, I haven’t confirmed this yet, but that’s my intuition.
EDIT: I have confirmed the tests pass without your changes. The break may be caused by your added functionality
You can pull the tests down from useDateTime Code Sandbox with Testsin a new Create React App with "export to zip" to confirm (Code Sandbox doesn't seem to play well with @jest/globals, and export to Zip wasn't working for me either).
I've included a test that I believe will fail with your change, but you can confirm that for your implementation.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Hi, thanks for sharing, and I'm glad it's useful to you! Full disclosure, this is a cleanup of the first custom hook I ever wrote around 2 years ago. I glossed over that part in my cleanup because I assumed it was right, but I wrote it before I fully understood hooks (even now, with scenarios like this, they're kinda tricky).
Looking closer, it seems like the
clearTimeout
is completely redundant with the cleanup function - which is always called whenever the threshold changes, or when the component unmounts. I've removed it which should help with the confusion. Thanks for flagging, I wouldn't have found this redundancy otherwise.For the custom intervals, I don't think it would be hard to implement, you'd just need to change some of the math with
startOfThreshold
andmsUntilNext
. Making it fully featured in that sense, you could even add an offset to account for something like "the 3rd minute of every hour".I think it would also be some extra work to anchor the 15 minutes against the hour, rather than any 15 minutes - though it could also be another hard coded threshold.
At some point I might write about testing this - since timeouts and intervals can be tricky to test, and there's a few interesting edge cases.
I have extended your hook, to support amounts of thresholds (before it was just 1), converted the hook to TypeScript and remove the extra cleanup as you suggested:
Then I wanted to add some tests, but I can't get it to run properly, let alone pass green.
Any idea how to test our hook?
OK, I have the tests working now and they instantly revealed some bugs in the hook. Getting tests to run properly (for a react-native project if that matters) was the main challenge.
It involved a custom jest-preset to work around some promise related bug:
See solution and discussion
jest-preset.js
This test now runs green:
A bug it revealed was that the hook reported the estimated future date before the timeout actually happened. In your component this was not a problem, you did not know you were one second ahead of system time ;) That happened because it called
setDate
outside the timeout callback.So now everything works! Thanks again for the initial work!
I’ll take a look, but this change likely breaks its ability to immediately respond to threshold changes e.g. second to minute.
I also wouldn’t expect the setDate to change unless the time has actually passed by more than a second since it goes to the start of the threshold.
Again, I haven’t confirmed this yet, but that’s my intuition.EDIT: I have confirmed the tests pass without your changes. The break may be caused by your added functionality
You can pull the tests down from useDateTime Code Sandbox with Tests
in a new Create React Appwith "export to zip" to confirm (Code Sandbox doesn't seem to play well with@jest/globals
, andexport to Zip wasn't working for me either).I've included a test that I believe will fail with your change, but you can confirm that for your implementation.