Skip to content

Commit

Permalink
feat(useCounter): reworked with use of new resolveHookState function …
Browse files Browse the repository at this point in the history
…plus improved memory usage;

feat(resolveHookState): improved types;
  • Loading branch information
xobotyi committed Oct 31, 2019
1 parent 9b5d0f2 commit befcf84
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 65 deletions.
6 changes: 3 additions & 3 deletions src/__tests__/useCounter.test.ts
Expand Up @@ -187,15 +187,15 @@ describe('should `console.error` on unexpected inputs', () => {

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

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

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

spy.mockRestore();
});
Expand Down
116 changes: 60 additions & 56 deletions src/useCounter.ts
@@ -1,85 +1,89 @@
import { useCallback } from 'react';
import { useMemo } from 'react';
import useGetSet from './useGetSet';
import { HookState, InitialHookState, resolveHookState } from './util/resolveHookState';

export interface CounterActions {
inc: (delta?: number) => void;
dec: (delta?: number) => void;
get: () => number;
set: (value: number) => void;
reset: (value?: number) => void;
set: (value: HookState<number>) => void;
reset: (value?: HookState<number>) => void;
}

export default function useCounter(
initialValue: number = 0,
initialValue: InitialHookState<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);
let init = resolveHookState(initialValue);

typeof init !== 'number' && console.error('initialValue has to be a number, got ' + typeof initialValue);

if (typeof min === 'number') {
initialValue = Math.max(initialValue, min);
init = Math.max(init, 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);
init = Math.min(init, max);
} else if (max !== null) {
console.error('max has to be a number, got ' + typeof max);
}

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

function set(value: number): void {
const current = get();
return [
get(),
useMemo(() => {
const set = (newState: HookState<number>) => {
const prevState = get();
let rState = resolveHookState(newState, prevState);

if (current === value) {
return;
}
if (prevState !== rState) {
if (typeof min === 'number') {
rState = Math.max(rState, min);
}
if (typeof max === 'number') {
rState = Math.min(rState, max);
}

if (typeof min === 'number') {
value = Math.max(value, min);
}
if (typeof max === 'number') {
value = Math.min(value, max);
}
prevState !== rState && setInternal(rState);
}
};

current !== value && setInternal(value);
}
return {
get,
set,
inc: (delta: HookState<number> = 1) => {
const rDelta = resolveHookState(delta, get());

if (typeof rDelta !== 'number') {
console.error('delta has to be a number or function returning a number, got ' + typeof rDelta);
}

set((num: number) => num + rDelta);
},
dec: (delta: HookState<number> = 1) => {
const rDelta = resolveHookState(delta, get());

if (typeof rDelta !== 'number') {
console.error('delta has to be a number or function returning a number, got ' + typeof rDelta);
}

set((num: number) => num - rDelta);
},
reset: (value: HookState<number> = init) => {
const rValue = resolveHookState(value, get());

if (typeof rValue !== 'number') {
console.error('value has to be a number or function returning a number, got ' + typeof rValue);
}

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,
get,
set,
reset,
};

return [get(), actions];
init = rValue;
set(rValue);
},
};
}, [min, max]),
];
}
10 changes: 4 additions & 6 deletions src/util/resolveHookState.ts
Expand Up @@ -5,14 +5,12 @@ export type InitialHookState<S> = S | InitialStateSetter<S>;
export type HookState<S> = S | StateSetter<S>;
export type ResolvableHookState<S> = S | StateSetter<S> | InitialStateSetter<S>;

export function resolveHookState<S>(newState: S | InitialStateSetter<S>): S;
export function resolveHookState<S>(newState: Exclude<HookState<any>, StateSetter<any>>, currentState: S): S;
// tslint:disable-next-line:unified-signatures
export function resolveHookState<S>(newState: StateSetter<S>, currentState: S): S;
export function resolveHookState<S>(newState: ResolvableHookState<S>, currentState?: S): S {
export function resolveHookState<S, C extends S>(newState: StateSetter<S>, currentState: C): S;
export function resolveHookState<S, C extends S>(newState: ResolvableHookState<S>, currentState?: C): S;
export function resolveHookState<S, C extends S>(newState: ResolvableHookState<S>, currentState?: C): S {
if (typeof newState === 'function') {
return (newState as Function)(currentState);
}

return newState as S;
return newState;
}

0 comments on commit befcf84

Please sign in to comment.