Skip to content

Commit

Permalink
feat(useCounter): port useCounter from react-use (#751)
Browse files Browse the repository at this point in the history
This commit ports the useCounter hook from react-use. It is not the exact same implementation, but it retains the same API.

re #33

Co-authored-by: xobotyi <[email protected]>
  • Loading branch information
ArttuOll and xobotyi authored May 12, 2022
1 parent d458abd commit 510947b
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Coming from `react-use`? Check out our

- #### State

- [**`useCounter`**](https://react-hookz.github.io/web/?path=/docs/state-usecounter--example)
- Tracks a numeric value and offers functions for manipulating it.
- [**`useDebouncedState`**](https://react-hookz.github.io/web/?path=/docs/state-usedebouncedstate--example)
— Like `useSafeState` but its state setter is debounced.
- [**`useMap`**](https://react-hookz.github.io/web/?path=/docs/state-usemap--example) — Tracks the
Expand Down
4 changes: 2 additions & 2 deletions src/__docs__/migrating-from-react-use.story.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -650,11 +650,11 @@ Use [useToggle](/docs/state-usetoggle--example) instead
#### useCounter
Not implemented yet
Implemented as [useCounter](/docs/state-usecounter--example)
#### useNumber
Not implemented yet
Use [useCounter](/docs/state-usecounter--example) instead.
#### useList
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export {
IValidityState,
IUseValidatorReturn,
} from './useValidator/useValidator';
export { useCounter, CounterActions } from './useCounter/useCounter';

// Navigator
export {
Expand Down
35 changes: 35 additions & 0 deletions src/useCounter/__docs__/example.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { useCounter } from '../..';

export const Example: React.FC = () => {
const [min, { inc: incMin, dec: decMin }] = useCounter(1);
const [max, { inc: incMax, dec: decMax }] = useCounter(10);
const [value, { inc, dec, set, reset }] = useCounter(5, max, min);

return (
<div>
<div>
current: {value} [min: {min}; max: {max}]
</div>
<br />
Current value:
<button onClick={() => inc()}>Increment</button>
<button onClick={() => dec()}>Decrement</button>
<button onClick={() => inc(5)}>Increment (+5)</button>
<button onClick={() => dec(5)}>Decrement (-5)</button>
<button onClick={() => set(100)}>Set 100</button>
<button onClick={() => reset()}>Reset</button>
<button onClick={() => reset(25)}>Reset (25)</button>
<br />
<br />
Min value:
<button onClick={() => incMin()}>Increment</button>
<button onClick={() => decMin()}>Decrement</button>
<br />
<br />
Max value:
<button onClick={() => incMax()}>Increment</button>
<button onClick={() => decMax()}>Decrement</button>
</div>
);
};
55 changes: 55 additions & 0 deletions src/useCounter/__docs__/story.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks';
import { Example } from './example.stories';
import { ImportPath } from '../../storybookUtil/ImportPath';

<Meta title="State/useCounter" component={Example} />

# useCounter

Tracks a numeric value and offers functions for manipulating it.

> **_This hook provides stable API, meaning returned functions do not change between renders_**
#### Example

<Canvas>
<Story story={Example} inline />
</Canvas>

## Reference

```ts
export function useCounter(
initialValue: IInitialState<number> = 0,
max?: number,
min?: number
): [number, CounterActions];
```

#### Importing

<ImportPath />

#### Arguments

- _**initialValue**_ _`IInitialState<number>` (default 0)_ - initial value for the counter. A number
or function returning a number.
- _**max**_ _`number` (default `undefined`)_ - maximum value the counter is allowed to be set.
- _**min**_ _`number` (default `undefined`)_ - minimum value the counter is allowed to be set.

#### Return

1. **counter** - The current value of the counter.

2. **methods**

- **get** - Returns the current value of the counter.
- **inc** - Increments the counter by the given delta (by default, 1).
- **dec** - Decrements the counter by the given delta (by default, 1).
- **set** - Sets the counter to the given value.
- **reset** - Resets the counter to its initial value. If a value is given, it
is set as the new initial value and any future calls to `reset`
without arguments will reset the counter to the given value.

All methods respect the `max` and `min` parameters. Instead of numerical
arguments, they also accept functions returning numbers.
232 changes: 232 additions & 0 deletions src/useCounter/__tests__/dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import { act, renderHook } from '@testing-library/react-hooks/dom';
import { useCounter } from '../..';

describe('useCounter', () => {
it('should be defined', () => {
expect(useCounter).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useCounter());
expect(result.error).toBeUndefined();
});

it('should have default initial value of 0', () => {
const { result } = renderHook(() => useCounter());
const counter = result.current[0];
expect(counter).toEqual(0);
});

it('should accept custom initial value', () => {
const { result } = renderHook(() => useCounter(5));
const counter = result.current[0];
expect(counter).toEqual(5);
});

it('should accept function returning a number as initial value', () => {
const { result } = renderHook(() => useCounter(() => 5));
const counter = result.current[0];
expect(counter).toEqual(5);
});

it('should force initial value to be at least the given minimum value', () => {
const { result } = renderHook(() => useCounter(0, 10, 5));
const counter = result.current[0];
expect(counter).toEqual(5);
});

it('should force initial value to be at most the given maximum value', () => {
const { result } = renderHook(() => useCounter(10, 5));
const counter = result.current[0];
expect(counter).toEqual(5);
});

it('get returns the current counter value', () => {
const { result } = renderHook(() => useCounter(0));
const { get } = result.current[1];

act(() => {
expect(get()).toEqual(result.current[0]);
});
});

it('set sets the counter to any value', () => {
const { result } = renderHook(() => useCounter(0));
const { set } = result.current[1];

act(() => {
set(2);
});

expect(result.current[0]).toEqual(2);

act(() => {
set((current: number) => current + 5);
});

expect(result.current[0]).toEqual(7);

act(() => {
set(12);
});

expect(result.current[0]).toEqual(12);
});

it('set respects min and max parameters', () => {
const { result } = renderHook(() => useCounter(0, 10, 0));
const { set } = result.current[1];

act(() => {
set(-2);
});

expect(result.current[0]).toEqual(0);

act(() => {
set(12);
});

expect(result.current[0]).toEqual(10);
});

it('inc increments the counter by 1 if no delta given', () => {
const { result } = renderHook(() => useCounter(0));
const { inc } = result.current[1];

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

const counter = result.current[0];
expect(counter).toEqual(1);
});

it('inc increments the counter by the given delta', () => {
const { result } = renderHook(() => useCounter(0));
const { inc } = result.current[1];

act(() => {
inc(2);
});

expect(result.current[0]).toEqual(2);

act(() => {
inc((current) => current + 1);
});

expect(result.current[0]).toEqual(5);
});

it('inc respects min and max parameters', () => {
const { result } = renderHook(() => useCounter(0, 5, 0));
const { inc } = result.current[1];

act(() => {
inc(-2);
});

expect(result.current[0]).toEqual(0);

act(() => {
inc(12);
});

expect(result.current[0]).toEqual(5);
});

it('dec decrements the counter by 1 if no delta given', () => {
const { result } = renderHook(() => useCounter(0));
const { dec } = result.current[1];

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

const counter = result.current[0];
expect(counter).toEqual(-1);
});

it('dec decrements the counter by the given delta', () => {
const { result } = renderHook(() => useCounter(0));
const { dec } = result.current[1];

act(() => {
dec(2);
});

expect(result.current[0]).toEqual(-2);

act(() => {
dec((current) => current + 1);
});

expect(result.current[0]).toEqual(-1);
});

it('dec respects min and max parameters', () => {
const { result } = renderHook(() => useCounter(0, 5, 0));
const { dec } = result.current[1];

act(() => {
dec(2);
});

expect(result.current[0]).toEqual(0);

act(() => {
dec(-12);
});

expect(result.current[0]).toEqual(5);
});

it('reset without arguments sets the counter to its initial value', () => {
const { result } = renderHook(() => useCounter(0));
const { reset, inc } = result.current[1];

act(() => {
inc();
reset();
});

expect(result.current[0]).toEqual(0);
});

it('reset with argument sets the counter to its new initial value', () => {
const { result } = renderHook(() => useCounter(0));
const { reset, inc } = result.current[1];

act(() => {
inc();
reset(5);
});

expect(result.current[0]).toEqual(5);

act(() => {
inc();
reset();
});

expect(result.current[0]).toEqual(0);
});

it('reset respects min and max parameters', () => {
const { result } = renderHook(() => useCounter(0, 10, 0));
const { reset } = result.current[1];

act(() => {
reset(25);
});

expect(result.current[0]).toEqual(10);

act(() => {
reset(-10);
});

expect(result.current[0]).toEqual(0);
});
});
13 changes: 13 additions & 0 deletions src/useCounter/__tests__/ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { renderHook } from '@testing-library/react-hooks/server';
import { useCounter } from '../..';

describe('useCounter', () => {
it('should be defined', () => {
expect(useCounter).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useCounter());
expect(result.error).toBeUndefined();
});
});
Loading

0 comments on commit 510947b

Please sign in to comment.