-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ca4f179
commit 1595b81
Showing
5 changed files
with
133 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
"use client"; | ||
|
||
import React from 'react'; | ||
import {useCooldownState} from "@/../src"; | ||
|
||
const Page = () => { | ||
const [state, setState, forceUpdate] = useCooldownState(true, 1000); | ||
return ( | ||
<> | ||
<h1> | ||
My favorite color is: {state ? "green" : "red"} | ||
</h1> | ||
<button onClick={() => setState(!state)}>Change Opinion</button> | ||
<button onClick={() => forceUpdate(!state)}>Change Opinion Immediately</button> | ||
</> | ||
); | ||
}; | ||
|
||
export default Page; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { useCallback, useState } from "react"; | ||
|
||
/** | ||
* This hook wraps the standard useState hook but provides a way to block state updates for a provided amount of time. | ||
* Further state updates during this cooldown time are discarded. | ||
* @param initialState - The initialState passed to the useState hook. | ||
* @param delay - The amount of time in milliseconds to block consecutive state updates. | ||
* @return An array containing the read-only state (index 0), the setState function (index 1) which enforces the | ||
* cooldown on updates, and another setState function (index 2) which forces a state update independent of the cooldown. | ||
* @example | ||
* ```tsx | ||
* const Page = () => { | ||
* const [state, setState, forceUpdate] = useCooldownState(true, 1000); | ||
* return ( | ||
* <> | ||
* <h1> | ||
* My favorite color is: {state ? "green" : "red"} | ||
* </h1> | ||
* <button onClick={() => setState(!state)}>Change Opinion</button> | ||
* <button onClick={() => forceUpdate(!state)}>Change Opinion Immediately</button> | ||
* </> | ||
* ); | ||
* }; | ||
* ``` | ||
*/ | ||
|
||
function useCooldownState<T>(initialState: T, delay: number): [T, (newValue: T) => void, (newValue: T) => void] { | ||
const [state, setState] = useState(initialState); | ||
const [blockUpdate, setBlockUpdate] = useState(false); | ||
|
||
const setStateWrapper = useCallback((newState: T) => { | ||
if (blockUpdate) return; | ||
setState(newState); | ||
setBlockUpdate(true); | ||
setTimeout(() => setBlockUpdate(false), delay); | ||
}, [blockUpdate]); | ||
|
||
return [state, setStateWrapper, setState]; | ||
} | ||
|
||
export default useCooldownState; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { act, renderHook } from "@testing-library/react"; | ||
import { useCooldownState } from "../src"; | ||
|
||
describe("useCooldownState", () => { | ||
beforeEach(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
test("should initialize with the initial state", () => { | ||
const { result } = renderHook(() => useCooldownState(true, 1000)); | ||
const [state] = result.current; | ||
|
||
expect(state).toBe(true); | ||
}); | ||
|
||
test("should update state when setState is called and block subsequent updates for the cooldown period", () => { | ||
const { result } = renderHook(() => useCooldownState(true, 1000)); | ||
|
||
// Set state to false (should work immediately) | ||
act(() => { | ||
result.current[1](false); | ||
}); | ||
|
||
expect(result.current[0]).toBe(false); // state should change | ||
|
||
// Try to set state again before the cooldown ends (should not work) | ||
act(() => { | ||
result.current[1](true); | ||
}); | ||
|
||
expect(result.current[0]).toBe(false); // state should not change | ||
|
||
// Advance time by 1000ms to simulate cooldown completion | ||
act(() => { | ||
jest.advanceTimersByTime(1000); | ||
}); | ||
|
||
// Try to set state again after cooldown (should work) | ||
act(() => { | ||
result.current[1](true); | ||
}); | ||
|
||
expect(result.current[0]).toBe(true); // state should now change*/ | ||
}); | ||
|
||
test("should force update the state using forceUpdate regardless of the cooldown", () => { | ||
const { result } = renderHook(() => useCooldownState(true, 1000)); | ||
|
||
// Set state to false using forceUpdate (should bypass cooldown) | ||
act(() => { | ||
result.current[2](false); | ||
}); | ||
|
||
expect(result.current[0]).toBe(false); // state should change immediately | ||
|
||
// Try to force update again (should work immediately) | ||
act(() => { | ||
result.current[2](true); | ||
}); | ||
|
||
expect(result.current[0]).toBe(true); // state should change immediately | ||
}); | ||
}); |