Skip to content

Commit

Permalink
feat: use stricter TS config and fix issues caused by this
Browse files Browse the repository at this point in the history
  • Loading branch information
xobotyi committed May 6, 2021
1 parent 881a750 commit 6af7867
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 65 deletions.
40 changes: 20 additions & 20 deletions src/useMediatedState.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
import { useCallback, useRef } from 'react';
import { resolveHookState } from './util/resolveHookState';
import { Dispatch, useCallback, useRef } from 'react';
import { IInitialState, INextState, resolveHookState } from './util/resolveHookState';
import { useSafeState } from './useSafeState';

export function useMediatedState<S>(
initialState?: S | (() => S)
): [S, (value: S | (() => S)) => void];
export function useMediatedState<S, R>(
initialState?: S | (() => S),
mediator?: (state: R) => S
): [S, (value: R | ((prevState: S) => S)) => void];
export function useMediatedState<State>(
initialState?: IInitialState<State>
): [State, Dispatch<State>];
export function useMediatedState<State, RawState>(
initialState: IInitialState<State>,
mediator?: (state: RawState) => State
): [State, Dispatch<INextState<RawState, State>>];

/**
* Like `useState`, but every value set is passed through a mediator function.
*/
export function useMediatedState<S, R>(
initialState?: S | (() => S),
mediator?: (state: R) => S
): [S, (value: R | ((prevState: S) => R)) => void] {
const [state, setState] = useSafeState<S>(initialState);
export function useMediatedState<State, RawState>(
initialState?: IInitialState<State>,
mediator?: (state: RawState | undefined) => State
): [State | undefined, Dispatch<INextState<RawState, State | undefined>>] {
const [state, setState] = useSafeState(initialState);
const mediatorRef = useRef(mediator);

// this is required to make API stable
mediatorRef.current = mediator;

return [
state,
useCallback((value: R) => {
if (mediator) {
setState((prevState) =>
mediatorRef.current(resolveHookState(value, (prevState as unknown) as R))
);
useCallback((value) => {
const m = mediatorRef.current;

if (m) {
setState((prevState) => m(resolveHookState<RawState, State | undefined>(value, prevState)));
} else {
setState((value as unknown) as S);
setState((value as unknown) as State);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []),
Expand Down
56 changes: 26 additions & 30 deletions src/useNetworkState.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { isBrowser, noop } from './util/const';
import { isBrowser } from './util/const';
import { IInitialState } from './util/resolveHookState';
import { useSafeState } from './useSafeState';
import { off, on } from './util/misc';
Expand Down Expand Up @@ -103,38 +103,34 @@ function getConnectionState(previousState?: IUseNetworkState): IUseNetworkState
/**
* Tracks the state of browser's network connection.
*/
export const useNetworkState: typeof navigator.connection extends undefined
? undefined
: (initialState?: IInitialState<IUseNetworkState>) => IUseNetworkState = isBrowser
? function useNetworkState(initialState?: IInitialState<IUseNetworkState>): IUseNetworkState {
const [state, setState] = useSafeState(initialState ?? getConnectionState);
export function useNetworkState(initialState?: IInitialState<IUseNetworkState>): IUseNetworkState {
const [state, setState] = useSafeState(initialState ?? getConnectionState);

useEffect(() => {
const handleStateChange = () => {
setState(getConnectionState);
};
useEffect(() => {
const handleStateChange = () => {
setState(getConnectionState);
};

on(window, 'online', handleStateChange, { passive: true });
on(window, 'offline', handleStateChange, { passive: true });
on(window, 'online', handleStateChange, { passive: true });
on(window, 'offline', handleStateChange, { passive: true });

// it is quite hard to test it in jsdom environment maybe will be improved in future
/* istanbul ignore next */
if (conn) {
on(conn, 'change', handleStateChange, { passive: true });
}
// it is quite hard to test it in jsdom environment maybe will be improved in future
/* istanbul ignore next */
if (conn) {
on(conn, 'change', handleStateChange, { passive: true });
}

return () => {
off(window, 'online', handleStateChange);
off(window, 'offline', handleStateChange);
return () => {
off(window, 'online', handleStateChange);
off(window, 'offline', handleStateChange);

/* istanbul ignore next */
if (conn) {
off(conn, 'change', handleStateChange);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/* istanbul ignore next */
if (conn) {
off(conn, 'change', handleStateChange);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return state;
}
: (noop as () => undefined);
return state;
}
2 changes: 1 addition & 1 deletion src/useRerender.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { useSafeState } from './useSafeState';

const stateChanger = (state) => !state;
const stateChanger = (state: boolean) => !state;

/**
* Return callback function that re-renders component.
Expand Down
6 changes: 4 additions & 2 deletions src/useSafeState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ export function useSafeState<S = undefined>(): [
*
* @param initialState
*/
export function useSafeState<S>(initialState?: S | (() => S)): [S, Dispatch<SetStateAction<S>>] {
export function useSafeState<S>(
initialState?: S | (() => S)
): [S | undefined, Dispatch<SetStateAction<S>>] {
const [state, setState] = useState(initialState);
const isMounted = useIsMounted();

return [
state,
useCallback((value) => {
if (isMounted()) setState(value);
if (isMounted()) setState(value as typeof state);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) as Dispatch<SetStateAction<S>>,
];
Expand Down
4 changes: 2 additions & 2 deletions src/useToggle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { IInitialState, INewState, resolveHookState } from './util/resolveHookState';
import { IInitialState, INextState, resolveHookState } from './util/resolveHookState';
import { useSafeState } from './useSafeState';

/**
Expand All @@ -11,7 +11,7 @@ import { useSafeState } from './useSafeState';
*/
export function useToggle(
initialState: IInitialState<boolean> = false
): [boolean, (nextState?: INewState<boolean>) => void] {
): [boolean, (nextState?: INextState<boolean>) => void] {
// We dont use useReducer (which would end up with less code), because exposed
// action does not provide functional updates feature.
// Therefore we have to create and expose our own state setter with
Expand Down
2 changes: 2 additions & 0 deletions src/util/const.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export const noop = (): void => {};

export const bypass = <T>(v: T): T => v;

export const isBrowser =
typeof window !== 'undefined' &&
typeof navigator !== 'undefined' &&
Expand Down
16 changes: 11 additions & 5 deletions src/util/resolveHookState.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
export type IInitialState<S> = S | (() => S);
export type INewState<S> = S | ((prevState: S) => S);
export type IInitialState<State> = State | (() => State);
export type INextState<State, PrevState = State> = State | ((prevState: PrevState) => State);

export function resolveHookState<S>(nextState: IInitialState<S>): S;
export function resolveHookState<S>(nextState: INewState<S>, prevState: S): S;
export function resolveHookState<S>(nextState: IInitialState<S> | INewState<S>, prevState?: S): S {
export function resolveHookState<State>(nextState: IInitialState<State>): State;
export function resolveHookState<State, PrevState = State>(
nextState: INextState<State, PrevState>,
prevState: PrevState
): State;
export function resolveHookState<State, PrevState = State>(
nextState: IInitialState<State> | INextState<State, PrevState>,
prevState?: PrevState
): State {
if (typeof nextState === 'function') return (nextState as CallableFunction)(prevState);

return nextState;
Expand Down
6 changes: 4 additions & 2 deletions tests/dom/util/misc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ describe('misc', () => {
on((obj as unknown) as EventTarget, 'evtName', cb);
expect(obj.addEventListener).toBeCalledWith('evtName', cb);
});
it("should not throw in case 'undefined' passed", () => {
it("should not throw in case 'undefined' element passed", () => {
expect(() => {
// @ts-expect-error testing inappropriate usage
on(undefined, 'evtName', () => {});
}).not.toThrow();
});
Expand All @@ -29,8 +30,9 @@ describe('misc', () => {
expect(obj.removeEventListener).toBeCalledWith('evtName', cb);
});

it("should not throw in case 'undefined' passed", () => {
it("should not throw in case 'undefined' element passed", () => {
expect(() => {
// @ts-expect-error testing inappropriate usage
off(undefined, 'evtName', () => {});
}).not.toThrow();
});
Expand Down
14 changes: 12 additions & 2 deletions tests/ssr/useNetworkState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,18 @@ describe(`useNetworkState`, () => {
});

it('should have undefined state', () => {
const hook = renderHook(() => useNetworkState(), { initialProps: false });
const hook = renderHook(() => useNetworkState());

expect(hook.result.current).toBeUndefined();
expect(hook.result.current).toStrictEqual({
downlink: undefined,
downlinkMax: undefined,
effectiveType: undefined,
online: undefined,
previous: undefined,
rtt: undefined,
saveData: undefined,
since: undefined,
type: undefined,
});
});
});
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"jsx": "react"
"jsx": "react",
"strict": true,
"strictNullChecks": true,
},
"exclude": [
"node_modules",
Expand Down

0 comments on commit 6af7867

Please sign in to comment.