Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"check-types": "turbo run check-types"
Expand Down
1 change: 1 addition & 0 deletions packages/tiny-react-hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useDisclosure';
1 change: 1 addition & 0 deletions packages/tiny-react-hooks/src/useDisclosure/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useDisclosure';
21 changes: 21 additions & 0 deletions packages/tiny-react-hooks/src/useDisclosure/useDisclosure.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useDisclosure } from './useDisclosure';

export default function Component() {
const {
isOpen,
onClose,
onOpen,
onToggle,
} = useDisclosure();

return (
<>
<p>
Value is <code>{isOpen.toString()}</code>
</p>
<button onClick={onClose}>Open</button>
<button onClick={onOpen}>Close</button>
<button onClick={onToggle}>Toggle</button>
</>
)
}
5 changes: 5 additions & 0 deletions packages/tiny-react-hooks/src/useDisclosure/useDisclosure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
A simple hook to play with a boolean.
Use cases:
- manage modal state
- manage popover state
- manage boolean value
105 changes: 105 additions & 0 deletions packages/tiny-react-hooks/src/useDisclosure/useDisclosure.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { act, renderHook } from '@testing-library/react';
import { useDisclosure } from './useDisclosure';

describe('useDisclosure()', () => {
it('should return correct value and methods', () => {
const { result } = renderHook(() => useDisclosure());

expect(result.current.isOpen).toBe(false);
expect(typeof result.current.isOpen).toBe('boolean');
expect(typeof result.current.onOpen).toBe('function');
expect(typeof result.current.onClose).toBe('function');
expect(typeof result.current.onToggle).toBe('function');
});


describe('with default value', () => {
describe('with correct default value as boolean', () => {
describe('should work with default value', () => {
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);
});
it('should return isOpen with false when nothing is passed as argument', () => {
const { result } = renderHook(() => useDisclosure());
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('onOpen', () => {
it('should set to true', () => {
const { result } = renderHook(() => useDisclosure(false));

act(() => {
result.current.onOpen();
});

expect(result.current.isOpen).toBe(true);
});
});

describe('onClose', () => {
it('should set to false', () => {
const { result } = renderHook(() => useDisclosure(true));

act(() => {
result.current.onClose();
});

expect(result.current.isOpen).toBe(false);
})
});

describe('onToggle', () => {
describe('toggle the value when false', () => {
it('should set to true', () => {
const { result } = renderHook(() => useDisclosure(false));
act(() => {
result.current.onToggle();
});

expect(result.current.isOpen).toBe(true);
});
});
describe('toggle the value when true', () => {
it('should set to false', () => {
const { result } = renderHook(() => useDisclosure(true));
act(() => {
result.current.onToggle();
});

expect(result.current.isOpen).toBe(false);
});
});
describe('toggle multiple time', () => {
it('should set to correct open state', () => {
const { result } = renderHook(() => useDisclosure(false));
act(() => {
result.current.onToggle(); // true
result.current.onToggle(); // false
result.current.onToggle(); // true
result.current.onToggle(); // false
result.current.onToggle(); // true -> result
});

expect(result.current.isOpen).toBe(true);
});
});
});
});
43 changes: 43 additions & 0 deletions packages/tiny-react-hooks/src/useDisclosure/useDisclosure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useCallback, useState } from 'react';

type UseDisclosureReturn = {
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
onToggle: () => void;
}

/**
* Custom hook that handles boolean state with useful utility functions.
* @param {boolean} [defaultValue] - The initial value 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
* @example
* ```tsx
* 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<boolean>(defaultValue);

const onOpen = useCallback(() => {
setOpen(true);
}, []);

const onClose = useCallback(() => {
setOpen(false);
}, []);

const onToggle = useCallback(() => {
setOpen((isOpening) => !isOpening);
}, []);

return {
isOpen,
onOpen,
onClose,
onToggle,
};
}
4 changes: 4 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"dependsOn": [],
"cache": false
},
"test": {
"outputs": [],
"cache": false
},
"dev": {
"dependsOn": ["^build"],
"outputs": [],
Expand Down