Skip to content
Open
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
101 changes: 101 additions & 0 deletions docs/useSwitcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# `useSwitcher`

React hook that tracks a boolean state with dedicated functions for turning on, off, and toggling.

Similar to `useToggle`, but instead of a single toggle function, it provides three separate control functions for more explicit state management.

## Usage

```jsx
import { useSwitcher } from 'react-use';

const Demo = () => {
const [isOn, turnOn, turnOff, toggle] = useSwitcher();

return (
<div>
<div>State: {isOn ? 'ON' : 'OFF'}</div>
<button onClick={turnOn}>Turn On</button>
<button onClick={turnOff}>Turn Off</button>
<button onClick={toggle}>Toggle</button>
</div>
);
};
```

## Examples

### With initial value

```jsx
const [isOpen, openModal, closeModal, toggleModal] = useSwitcher(true);
```

### In a modal component

```jsx
const Modal = () => {
const [isOpen, openModal, closeModal] = useSwitcher(false);

return (
<>
<button onClick={openModal}>Open Modal</button>
{isOpen && (
<div className="modal">
<h2>Modal Content</h2>
<button onClick={closeModal}>Close</button>
</div>
)}
</>
);
};
```

### In a sidebar component

```jsx
const Sidebar = () => {
const [isVisible, showSidebar, hideSidebar, toggleSidebar] = useSwitcher(true);

return (
<>
<button onClick={toggleSidebar}>
{isVisible ? 'Hide' : 'Show'} Sidebar
</button>
<aside style={{ display: isVisible ? 'block' : 'none' }}>
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</nav>
<button onClick={hideSidebar}>Close</button>
</aside>
</>
);
};
```

## Reference

```typescript
const [state, turnOn, turnOff, toggle] = useSwitcher(defaultValue?);
```

### Parameters

- `defaultValue`: `boolean` - Initial state value. Defaults to `false`.

### Returns

Returns a tuple with the following elements:

- `state`: `boolean` - Current state value.
- `turnOn`: `() => void` - Function that sets state to `true`.
- `turnOff`: `() => void` - Function that sets state to `false`.
- `toggle`: `() => void` - Function that toggles the state.

## Related hooks

- [`useToggle`](./useToggle.md) - Similar hook with a single toggle function
- [`useBoolean`](./useBoolean.md) - Alias for `useToggle`
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export { default as useSpeech } from './useSpeech';
export { default as useStartTyping } from './useStartTyping';
export { useStateWithHistory } from './useStateWithHistory';
export { default as useStateList } from './useStateList';
export { default as useSwitcher } from './useSwitcher';
export { default as useThrottle } from './useThrottle';
export { default as useThrottleFn } from './useThrottleFn';
export { default as useTimeout } from './useTimeout';
Expand Down
18 changes: 18 additions & 0 deletions src/useSwitcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useCallback, useState } from 'react';

/**
* @param defaultValue initial value of the switch. Default {@link false}
* @example
* const [isOpen, turnIsOpenOn, turnIsOpenOff, toggleIsOpen] = useSwitcher();
*/
const useSwitcher = (defaultValue: boolean = false): readonly [boolean, () => void, () => void, () => void] => {
const [state, setState] = useState(defaultValue);

const turnOn = useCallback(() => setState(true), []);
const turnOff = useCallback(() => setState(false), []);
const toggle = useCallback(() => setState((s) => !s), []);

return [state, turnOn, turnOff, toggle] as const;
};

export default useSwitcher;
164 changes: 164 additions & 0 deletions tests/useSwitcher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { act, renderHook } from '@testing-library/react-hooks';
import useSwitcher from '../src/useSwitcher';

const setUp = (initialValue?: boolean) => renderHook(() => useSwitcher(initialValue));

describe('useSwitcher', () => {
it('should init state to false by default', () => {
const { result } = setUp();

expect(result.current[0]).toBe(false);
expect(typeof result.current[1]).toBe('function');
expect(typeof result.current[2]).toBe('function');
expect(typeof result.current[3]).toBe('function');
});

it('should init state to false when explicitly passed', () => {
const { result } = setUp(false);

expect(result.current[0]).toBe(false);
});

it('should init state to true when passed', () => {
const { result } = setUp(true);

expect(result.current[0]).toBe(true);
});

it('should turn on from false', () => {
const { result } = setUp(false);
const [, turnOn] = result.current;

expect(result.current[0]).toBe(false);

act(() => {
turnOn();
});

expect(result.current[0]).toBe(true);
});

it('should stay on when turn on is called while already on', () => {
const { result } = setUp(true);
const [, turnOn] = result.current;

expect(result.current[0]).toBe(true);

act(() => {
turnOn();
});

expect(result.current[0]).toBe(true);
});

it('should turn off from true', () => {
const { result } = setUp(true);
const [, , turnOff] = result.current;

expect(result.current[0]).toBe(true);

act(() => {
turnOff();
});

expect(result.current[0]).toBe(false);
});

it('should stay off when turn off is called while already off', () => {
const { result } = setUp(false);
const [, , turnOff] = result.current;

expect(result.current[0]).toBe(false);

act(() => {
turnOff();
});

expect(result.current[0]).toBe(false);
});

it('should toggle state from true to false', () => {
const { result } = setUp(true);
const [, , , toggle] = result.current;

act(() => {
toggle();
});

expect(result.current[0]).toBe(false);
});

it('should toggle state from false to true', () => {
const { result } = setUp(false);
const [, , , toggle] = result.current;

act(() => {
toggle();
});

expect(result.current[0]).toBe(true);
});

it('should toggle multiple times correctly', () => {
const { result } = setUp(false);
const [, , , toggle] = result.current;

expect(result.current[0]).toBe(false);

act(() => {
toggle();
});
expect(result.current[0]).toBe(true);

act(() => {
toggle();
});
expect(result.current[0]).toBe(false);

act(() => {
toggle();
});
expect(result.current[0]).toBe(true);
});

it('should work with all functions in combination', () => {
const { result } = setUp(false);
const [, turnOn, turnOff, toggle] = result.current;

expect(result.current[0]).toBe(false);

act(() => {
turnOn();
});
expect(result.current[0]).toBe(true);

act(() => {
toggle();
});
expect(result.current[0]).toBe(false);

act(() => {
turnOn();
});
expect(result.current[0]).toBe(true);

act(() => {
turnOff();
});
expect(result.current[0]).toBe(false);
});

it('should maintain function references across re-renders', () => {
const { result, rerender } = setUp(false);

const [, turnOn1, turnOff1, toggle1] = result.current;

rerender();

const [, turnOn2, turnOff2, toggle2] = result.current;

expect(turnOn1).toBe(turnOn2);
expect(turnOff1).toBe(turnOff2);
expect(toggle1).toBe(toggle2);
});
});