feat: add min/max to useNumber

This commit is contained in:
Vadim Dalecky 2019-08-17 18:18:20 +02:00 committed by GitHub
commit 586faabe74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 202 additions and 30 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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);
@ -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();
});
});

View File

@ -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,
@ -26,6 +82,4 @@ const useCounter = (initialValue: number = 0): [number, CounterActions] => {
};
return [get(), actions];
};
export default useCounter;
}