diff --git a/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.demo.tsx b/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.demo.tsx index dbde126..2a8ebb6 100644 --- a/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.demo.tsx +++ b/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.demo.tsx @@ -1,12 +1,24 @@ import { useDisclosure } from './useDisclosure'; -export default function Component() { +export function Component() { const { isOpen, onClose, onOpen, onToggle, } = useDisclosure(); + // const { + // isOpen, + // onClose, + // onOpen, + // onToggle, + // } = useDisclosure(false); + // const { + // isOpen, + // onClose, + // onOpen, + // onToggle, + // } = useDisclosure(() => false); return ( <> @@ -18,4 +30,4 @@ export default function Component() { ) -} \ No newline at end of file +} diff --git a/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.test.ts b/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.test.ts index ab7a4ee..c1ef5d8 100644 --- a/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.test.ts +++ b/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.test.ts @@ -12,10 +12,16 @@ describe('useDisclosure()', () => { expect(typeof result.current.onToggle).toBe('function'); }); + describe('with no default value', () => { + it('should return isOpen with false when nothing is passed as argument', () => { + const { result } = renderHook(() => useDisclosure()); + expect(result.current.isOpen).toBe(false); + }); + }); describe('with default value', () => { - describe('with correct default value as boolean', () => { - describe('should work with default value', () => { + describe('with correct default value', () => { + describe('with default value is a boolean', () => { it('should return isOpen with true', () => { const { result } = renderHook(() => useDisclosure(true)); expect(result.current.isOpen).toBe(true); @@ -24,20 +30,39 @@ describe('useDisclosure()', () => { const { result } = renderHook(() => useDisclosure(false)); expect(result.current.isOpen).toBe(false); }); - it('should return isOpen with false when nothing is passed as argument', () => { - const { result } = renderHook(() => useDisclosure()); + }); + + describe('with default value is function', () => { + it('should return isOpen with true', () => { + const { result } = renderHook(() => useDisclosure(() => true)); + expect(result.current.isOpen).toBe(true); + }); + it('should return isOpen with false', () => { + const { result } = renderHook(() => useDisclosure(() => false)); expect(result.current.isOpen).toBe(false); }); }); }); describe('with incorrect default value type', () => { - it('should throw an error', () => { - const nonBoolean = '' as never; - vi.spyOn(console, 'error').mockImplementation(() => vi.fn()); - expect(() => { - renderHook(() => useDisclosure(nonBoolean)); - }).toThrowError('defaultValue must be a boolean value'); - vi.resetAllMocks(); + describe('with default value is a boolean', () => { + it('should throw an error', () => { + const nonBoolean = '' as never; + vi.spyOn(console, 'error').mockImplementation(() => vi.fn()); + expect(() => { + renderHook(() => useDisclosure(nonBoolean)); + }).toThrowError('defaultValue must be a boolean value'); + vi.resetAllMocks(); + }); + }); + describe('with default value is a function', () => { + it('should throw an error', () => { + const nonBoolean = () => '' as never; + vi.spyOn(console, 'error').mockImplementation(() => vi.fn()); + expect(() => { + renderHook(() => useDisclosure(nonBoolean)); + }).toThrowError('defaultValue must be a boolean value'); + vi.resetAllMocks(); + }); }); }); }); diff --git a/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.ts b/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.ts index 1ff7267..ca79a78 100644 --- a/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.ts +++ b/packages/tiny-react-hooks/src/useDisclosure/useDisclosure.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; type UseDisclosureReturn = { isOpen: boolean; @@ -7,9 +7,11 @@ type UseDisclosureReturn = { onToggle: () => void; } +type DisclosureDefaultValue = boolean | (() => boolean); + /** * Custom hook that handles boolean state with useful utility functions. - * @param {boolean} [defaultValue] - The initial value for the boolean state (default is `false`). + * @param {DisclosureDefaultValue} [defaultValue] - The initial value or produce default value function for the boolean state (default is `false`). * @returns {UseDisclosureReturn} An object containing the boolean state value and utility functions to manipulate the state. * @throws Will throw an error if `defaultValue` is an invalid boolean value. * @public @@ -18,9 +20,10 @@ type UseDisclosureReturn = { * const { isOpen, onOpen, onClose, onToggle } = UseDisclosureReturn(true); * ``` */ -export function useDisclosure(defaultValue = false): UseDisclosureReturn { - if (typeof defaultValue !== 'boolean') throw new Error('defaultValue must be a boolean value'); - const [isOpen, setOpen] = useState(defaultValue); +export function useDisclosure(init: DisclosureDefaultValue = false): UseDisclosureReturn { + const initialValue = useRef(typeof init === 'function' ? init() : init); + if (typeof initialValue.current !== 'boolean') throw new Error('defaultValue must be a boolean value'); + const [isOpen, setOpen] = useState(initialValue.current); const onOpen = useCallback(() => { setOpen(true);