Skip to content

Commit

Permalink
feat: add min/max to useNumber
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich authored Aug 17, 2019
2 parents 0ce8623 + 909f9bd commit 586faab
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 30 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
- [`useObservable`](./docs/useObservable.md) — tracks latest value of an `Observable`.
- [`useSetState`](./docs/useSetState.md) — creates `setState` method which works like `this.setState`. [![][img-demo]](https://codesandbox.io/s/n75zqn1xp0)
- [`useToggle` and `useBoolean`](./docs/useToggle.md) — tracks state of a boolean.
- [`useCounter` and `useNumber`](./docs/useCounter.md) — tracks state of a number.
- [`useCounter` and `useNumber`](./docs/useCounter.md) — tracks state of a number. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usecounter--demo)
- [`useList`](./docs/useList.md) — tracks state of an array.
- [`useMap`](./docs/useMap.md) — tracks state of an object.

Expand Down
49 changes: 40 additions & 9 deletions docs/useCounter.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,50 @@ React state hook that tracks a numeric value.
import {useCounter, useNumber} from 'react-use';

const Demo = () => {
const [value, {inc, dec, get, set, reset}] = useCounter(5);
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>{value} is {get()}</div>
<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>
<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>
);
};
```


## Reference

```ts
const [ current, { inc, dec, get, set, reset } ] = useCounter(initial: number, max: number | null = null, 20: number | null = null);
```
- `current` - current counter value;
- `get(): number` - getter of current counter value;
- `inc(delta: number): void` - increment current value;
- `dec(delta: number): void` - decrement current value;
- `set(value: number): void` - set arbitrary value;
- `reset(value: number): void` - as the `set`, but also will assign value by reference to the `initial` parameter;
19 changes: 16 additions & 3 deletions src/__stories__/useCounter.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,33 @@ import { useCounter } from '..';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const [value, { inc, dec, get, set, reset }] = useCounter(5);
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>
{value} is {get()}
current: {value} [min: {min}; max: {max}]
</div>
<button onClick={() => inc()}>Increment</button>
<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>
);
};
Expand Down
86 changes: 80 additions & 6 deletions src/__tests__/useCounter.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { act, renderHook } from '@testing-library/react-hooks';
import useCounter from '../useCounter';

const setUp = (initialValue?: number) => renderHook(() => useCounter(initialValue));
const setUp = (initialValue?: number, max: number | null = null, min: number | null = null) =>
renderHook(() => useCounter(initialValue, max, min));

it('should init counter and utils', () => {
const { result } = setUp(5);
Expand Down Expand Up @@ -121,8 +122,81 @@ it('should reset and set new original value', () => {
expect(get()).toBe(8);
});

it.todo('should log an error if initial value is other than a number');
it.todo('should log an error if increment value is other than a number');
it.todo('should log an error if increment value is a negative number');
it.todo('should log an error if decrement value is other than a number');
it.todo('should log an error if decrement value is a negative number');
it('should not exceed max value', () => {
const { result } = setUp(10, 5);
expect(result.current[0]).toBe(5);

const { get, inc, reset } = result.current[1];

act(() => reset(10));
expect(get()).toBe(5);

act(() => reset(4));
expect(get()).toBe(4);

act(() => inc());
expect(get()).toBe(5);

act(() => inc());
expect(get()).toBe(5);
});

it('should not exceed min value', () => {
const { result } = setUp(3, null, 5);
expect(result.current[0]).toBe(5);

const { get, dec, reset } = result.current[1];

act(() => reset(4));
expect(get()).toBe(5);

act(() => reset(6));
expect(get()).toBe(6);

act(() => dec());
expect(get()).toBe(5);

act(() => dec());
expect(get()).toBe(5);
});

describe('should `console.error` on unexpected inputs', () => {
it('on any of call parameters', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});

// @ts-ignore
setUp(false);
expect(spy.mock.calls[0][0]).toBe('initialValue has to be a number, got boolean');

// @ts-ignore
setUp(10, false);
expect(spy.mock.calls[1][0]).toBe('max has to be a number, got boolean');

// @ts-ignore
setUp(10, 5, {});
expect(spy.mock.calls[2][0]).toBe('min has to be a number, got object');

spy.mockRestore();
});

it('on any of returned methods has unexpected input', () => {
const { result } = setUp(10);
const { inc, dec, reset } = result.current[1];

const spy = jest.spyOn(console, 'error').mockImplementation(() => {});

// @ts-ignore
act(() => inc(false));
expect(spy.mock.calls[0][0]).toBe('delta has to be a number, got boolean');

// @ts-ignore
act(() => dec(false));
expect(spy.mock.calls[1][0]).toBe('delta has to be a number, got boolean');

// @ts-ignore
act(() => reset({}));
expect(spy.mock.calls[2][0]).toBe('value has to be a number, got object');

spy.mockRestore();
});
});
76 changes: 65 additions & 11 deletions src/useCounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,70 @@ export interface CounterActions {
reset: (value?: number) => void;
}

const useCounter = (initialValue: number = 0): [number, CounterActions] => {
const [get, set] = useGetSet<number>(initialValue);
const inc = useCallback((delta: number = 1) => set(get() + delta), []);
const dec = useCallback((delta: number = 1) => inc(-delta), []);
const reset = useCallback((value: number = initialValue) => {
initialValue = value;
set(value);
}, []);
export default function useCounter(
initialValue: number = 0,
max: number | null = null,
min: number | null = null
): [number, CounterActions] {
typeof initialValue !== 'number' && console.error('initialValue has to be a number, got ' + typeof initialValue);

if (typeof min === 'number') {
initialValue = Math.max(initialValue, min);
} else if (min !== null) {
console.error('min has to be a number, got ' + typeof min);
}

if (typeof max === 'number') {
initialValue = Math.min(initialValue, max);
} else if (max !== null) {
console.error('max has to be a number, got ' + typeof max);
}

const [get, setInternal] = useGetSet<number>(initialValue);

function set(value: number): void {
const current = get();

if (current === value) {
return;
}

if (typeof min === 'number') {
value = Math.max(value, min);
}
if (typeof max === 'number') {
value = Math.min(value, max);
}

current !== value && setInternal(value);
}

const inc = useCallback(
(delta: number = 1) => {
typeof delta !== 'number' && console.error('delta has to be a number, got ' + typeof delta);

set(get() + delta);
},
[max, min]
);
const dec = useCallback(
(delta: number = 1) => {
typeof delta !== 'number' && console.error('delta has to be a number, got ' + typeof delta);

set(get() - delta);
},
[max, min]
);
const reset = useCallback(
(value: number = initialValue) => {
typeof value !== 'number' && console.error('value has to be a number, got ' + typeof value);

initialValue = value;
set(value);
},
[max, min]
);

const actions = {
inc,
dec,
Expand All @@ -26,6 +82,4 @@ const useCounter = (initialValue: number = 0): [number, CounterActions] => {
};

return [get(), actions];
};

export default useCounter;
}

0 comments on commit 586faab

Please sign in to comment.