feat(prettier): make prettier a part of eslint.

Also reduce max line width to 100. And remove `lint:types` step for
commit sequence, it bothers when committing incomplete (wip) changes.
This commit is contained in:
xobotyi 2021-02-01 18:43:46 +03:00
parent f938995c80
commit e6fae504b8
No known key found for this signature in database
GPG Key ID: 36FA5C1DA5FEB62D
128 changed files with 792 additions and 388 deletions

View File

@ -8,7 +8,7 @@ indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
max_line_length = 100
[*.{ts, tsx}]
ij_typescript_enforce_trailing_comma = keep
@ -24,7 +24,7 @@ ij_typescript_catch_on_new_line = false
ij_typescript_spaces_within_interpolation_expressions = false
[*.md]
max_line_length = 120
max_line_length = 100
trim_trailing_whitespace = false
[COMMIT_EDITMSG]

22
.eslintrc.js Normal file
View File

@ -0,0 +1,22 @@
module.exports = {
extends: ['prettier/@typescript-eslint', 'react-app', 'plugin:prettier/recommended'],
plugins: ['prettier'],
rules: {
'prettier/prettier': [
'error',
{
singleQuote: true,
trailingComma: 'es5',
tabWidth: 2,
printWidth: 100,
semicolons: true,
quoteProps: 'as-needed',
jsxSingleQuote: false,
bracketSpacing: true,
jsxBracketSameLine: true,
arrowParens: 'always',
endOfLine: 'lf',
},
],
},
};

View File

@ -1,3 +0,0 @@
{
"extends": "react-app"
}

View File

@ -1,6 +0,0 @@
{
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2
}

View File

@ -16,10 +16,9 @@
"test": "jest --maxWorkers 2",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint {src,tests}/**/*.{ts,tsx}",
"lint": "eslint {src,tests,stories}/**/*.{ts,tsx}",
"lint:fix": "yarn lint --fix",
"lint:types": "tsc --noEmit",
"lint:prettier": "prettier --write src/**/**/*.{ts,tsx}",
"build:cjs": "tsc",
"build:es": "tsc -m esNext --outDir esm",
"build": "yarn build:cjs && yarn build:es",
@ -32,7 +31,7 @@
},
"husky": {
"hooks": {
"pre-commit": "yarn lint:types && lint-staged",
"pre-commit": "lint-staged",
"pre-push": "yarn lint && yarn clean && yarn build && yarn test"
}
},
@ -92,10 +91,12 @@
"babel-loader": "8.2.2",
"babel-plugin-dynamic-import-node": "2.3.3",
"eslint": "7.18.0",
"eslint-config-prettier": "^7.2.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"fork-ts-checker-webpack-plugin": "6.1.0",
@ -106,7 +107,7 @@
"keyboardjs": "2.6.4",
"lint-staged": "10.5.3",
"markdown-loader": "6.0.0",
"prettier": "2.2.1",
"prettier": "^2.2.1",
"raf-stub": "3.0.0",
"react": "17.0.1",
"react-dom": "17.0.1",
@ -151,7 +152,6 @@
"lint-staged": {
"src/**/**/*.{ts,tsx}": [
"eslint --fix",
"prettier --write",
"git add"
]
},

View File

@ -1,6 +1,11 @@
import useKey from '../useKey';
import createRenderProp from '../factory/createRenderProp';
const UseKey = createRenderProp(useKey, ({ filter, fn, deps, ...rest }) => [filter, fn, rest, deps]);
const UseKey = createRenderProp(useKey, ({ filter, fn, deps, ...rest }) => [
filter,
fn,
rest,
deps,
]);
export default UseKey;

View File

@ -16,9 +16,10 @@ const createBreakpoint = (
off(window, 'resize', setSideScreen);
};
});
const sortedBreakpoints = useMemo(() => Object.entries(breakpoints).sort((a, b) => (a[1] >= b[1] ? 1 : -1)), [
breakpoints,
]);
const sortedBreakpoints = useMemo(
() => Object.entries(breakpoints).sort((a, b) => (a[1] >= b[1] ? 1 : -1)),
[breakpoints]
);
const result = sortedBreakpoints.reduce((acc, [name, width]) => {
if (screen >= width) {
return name;

View File

@ -3,7 +3,9 @@ import { useEffect, useRef } from 'react';
import useSetState from '../useSetState';
import parseTimeRanges from '../misc/parseTimeRanges';
export interface HTMLMediaProps extends React.AudioHTMLAttributes<any>, React.VideoHTMLAttributes<any> {
export interface HTMLMediaProps
extends React.AudioHTMLAttributes<any>,
React.VideoHTMLAttributes<any> {
src: string;
}
@ -33,7 +35,9 @@ type createHTMLMediaHookReturn = [
];
export default function createHTMLMediaHook(tag: 'audio' | 'video') {
return (elOrProps: HTMLMediaProps | React.ReactElement<HTMLMediaProps>): createHTMLMediaHookReturn => {
return (
elOrProps: HTMLMediaProps | React.ReactElement<HTMLMediaProps>
): createHTMLMediaHookReturn => {
let element: React.ReactElement<any> | undefined;
let props: HTMLMediaProps;

View File

@ -8,7 +8,9 @@ interface Store<Action, State> {
dispatch: Dispatch<Action>;
}
type Middleware<Action, State> = (store: Store<Action, State>) => (next: Dispatch<Action>) => (action: Action) => void;
type Middleware<Action, State> = (
store: Store<Action, State>
) => (next: Dispatch<Action>) => (action: Action) => void;
function composeMiddleware<Action, State>(chain: Middleware<Action, State>[]) {
return (context: Store<Action, State>, dispatch: Dispatch<Action>) => {

View File

@ -4,11 +4,19 @@ const createReducerContext = <R extends React.Reducer<any, any>>(
reducer: R,
defaultInitialState: React.ReducerState<R>
) => {
const context = createContext<[React.ReducerState<R>, React.Dispatch<React.ReducerAction<R>>] | undefined>(undefined);
const context = createContext<
[React.ReducerState<R>, React.Dispatch<React.ReducerAction<R>>] | undefined
>(undefined);
const providerFactory = (props, children) => createElement(context.Provider, props, children);
const ReducerProvider: React.FC<{ initialState?: React.ReducerState<R> }> = ({ children, initialState }) => {
const state = useReducer<R>(reducer, initialState !== undefined ? initialState : defaultInitialState);
const ReducerProvider: React.FC<{ initialState?: React.ReducerState<R> }> = ({
children,
initialState,
}) => {
const state = useReducer<R>(
reducer,
initialState !== undefined ? initialState : defaultInitialState
);
return providerFactory({ value: state }, children);
};

View File

@ -1,7 +1,9 @@
import { createContext, createElement, useContext, useState } from 'react';
const createStateContext = <T>(defaultInitialValue: T) => {
const context = createContext<[T, React.Dispatch<React.SetStateAction<T>>] | undefined>(undefined);
const context = createContext<[T, React.Dispatch<React.SetStateAction<T>>] | undefined>(
undefined
);
const providerFactory = (props, children) => createElement(context.Provider, props, children);
const StateProvider: React.FC<{ initialValue?: T }> = ({ children, initialValue }) => {

View File

@ -7,9 +7,18 @@ export type IHookStateSetAction<S> = S | IHookStateSetter<S>;
export type IHookStateResolvable<S> = S | IHookStateInitialSetter<S> | IHookStateSetter<S>;
export function resolveHookState<S>(nextState: IHookStateInitAction<S>): S;
export function resolveHookState<S, C extends S>(nextState: IHookStateSetAction<S>, currentState?: C): S;
export function resolveHookState<S, C extends S>(nextState: IHookStateResolvable<S>, currentState?: C): S;
export function resolveHookState<S, C extends S>(nextState: IHookStateResolvable<S>, currentState?: C): S {
export function resolveHookState<S, C extends S>(
nextState: IHookStateSetAction<S>,
currentState?: C
): S;
export function resolveHookState<S, C extends S>(
nextState: IHookStateResolvable<S>,
currentState?: C
): S;
export function resolveHookState<S, C extends S>(
nextState: IHookStateResolvable<S>,
currentState?: C
): S {
if (typeof nextState === 'function') {
return nextState.length ? (nextState as Function)(currentState) : (nextState as Function)();
}

View File

@ -4,7 +4,10 @@ import { FunctionReturningPromise } from './misc/types';
export { AsyncState, AsyncFnReturn } from './useAsyncFn';
export default function useAsync<T extends FunctionReturningPromise>(fn: T, deps: DependencyList = []) {
export default function useAsync<T extends FunctionReturningPromise>(
fn: T,
deps: DependencyList = []
) {
const [state, callback] = useAsyncFn(fn, deps, {
loading: true,
});

View File

@ -24,7 +24,9 @@ export type AsyncState<T> =
value: T;
};
type StateFromFunctionReturningPromise<T extends FunctionReturningPromise> = AsyncState<PromiseType<ReturnType<T>>>;
type StateFromFunctionReturningPromise<T extends FunctionReturningPromise> = AsyncState<
PromiseType<ReturnType<T>>
>;
export type AsyncFnReturn<T extends FunctionReturningPromise = FunctionReturningPromise> = [
StateFromFunctionReturningPromise<T>,

View File

@ -13,7 +13,9 @@ const useAsyncRetry = <T>(fn: () => Promise<T>, deps: DependencyList = []) => {
const retry = useCallback(() => {
if (stateLoading) {
if (process.env.NODE_ENV === 'development') {
console.log('You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.');
console.log(
'You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.'
);
}
return;

View File

@ -25,7 +25,8 @@ type UseBatteryState =
| { isSupported: true; fetched: false } // battery API supported but not fetched yet
| (BatteryState & { isSupported: true; fetched: true }); // battery API supported and fetched
const nav: NavigatorWithPossibleBattery | undefined = typeof navigator === 'object' ? navigator : undefined;
const nav: NavigatorWithPossibleBattery | undefined =
typeof navigator === 'object' ? navigator : undefined;
const isBatteryApiSupported = nav && typeof nav.getBattery === 'function';
function useBatteryMock(): UseBatteryState {

View File

@ -26,7 +26,9 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] =
try {
// only strings and numbers casted to strings can be copied to clipboard
if (typeof value !== 'string' && typeof value !== 'number') {
const error = new Error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`);
const error = new Error(
`Cannot copy typeof ${typeof value} to clipboard, must be a string`
);
if (process.env.NODE_ENV === 'development') console.error(error);
setState({
value,

View File

@ -17,7 +17,8 @@ export default function useCounter(
): [number, CounterActions] {
let init = resolveHookState(initialValue);
typeof init !== 'number' && console.error('initialValue has to be a number, got ' + typeof initialValue);
typeof init !== 'number' &&
console.error('initialValue has to be a number, got ' + typeof initialValue);
if (typeof min === 'number') {
init = Math.max(init, min);
@ -59,7 +60,9 @@ export default function useCounter(
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);
console.error(
'delta has to be a number or function returning a number, got ' + typeof rDelta
);
}
set((num: number) => num + rDelta);
@ -68,7 +71,9 @@ export default function useCounter(
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);
console.error(
'delta has to be a number or function returning a number, got ' + typeof rDelta
);
}
set((num: number) => num - rDelta);
@ -77,7 +82,9 @@ export default function useCounter(
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);
console.error(
'value has to be a number or function returning a number, got ' + typeof rValue
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -11,7 +11,9 @@ const useCustomCompareEffect = <TDeps extends DependencyList>(
) => {
if (process.env.NODE_ENV !== 'production') {
if (!(deps instanceof Array) || !deps.length) {
console.warn('`useCustomCompareEffect` should not be used with no dependencies. Use React.useEffect instead.');
console.warn(
'`useCustomCompareEffect` should not be used with no dependencies. Use React.useEffect instead.'
);
}
if (deps.every(isPrimitive)) {
@ -21,7 +23,9 @@ const useCustomCompareEffect = <TDeps extends DependencyList>(
}
if (typeof depsEqual !== 'function') {
console.warn('`useCustomCompareEffect` should be used with depsEqual callback for comparing deps list');
console.warn(
'`useCustomCompareEffect` should be used with depsEqual callback for comparing deps list'
);
}
}

View File

@ -3,7 +3,11 @@ import useTimeoutFn from './useTimeoutFn';
export type UseDebounceReturn = [() => boolean | null, () => void];
export default function useDebounce(fn: Function, ms: number = 0, deps: DependencyList = []): UseDebounceReturn {
export default function useDebounce(
fn: Function,
ms: number = 0,
deps: DependencyList = []
): UseDebounceReturn {
const [isReady, cancel, reset] = useTimeoutFn(fn, ms);
useEffect(reset, deps);

View File

@ -7,7 +7,9 @@ const isPrimitive = (val: any) => val !== Object(val);
const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
if (process.env.NODE_ENV !== 'production') {
if (!(deps instanceof Array) || !deps.length) {
console.warn('`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.');
console.warn(
'`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.'
);
}
if (deps.every(isPrimitive)) {

View File

@ -1,6 +1,9 @@
import { useState } from 'react';
const useDefault = <TStateType>(defaultValue: TStateType, initialValue: TStateType | (() => TStateType)) => {
const useDefault = <TStateType>(
defaultValue: TStateType,
initialValue: TStateType | (() => TStateType)
) => {
const [value, setValue] = useState<TStateType | undefined | null>(initialValue);
if (value === undefined || value === null) {

View File

@ -26,7 +26,10 @@ const defaultState: DropAreaState = {
};
*/
const createProcess = (options: DropAreaOptions, mounted: boolean) => (dataTransfer: DataTransfer, event) => {
const createProcess = (options: DropAreaOptions, mounted: boolean) => (
dataTransfer: DataTransfer,
event
) => {
const uri = dataTransfer.getData('text/uri-list');
if (uri) {

View File

@ -10,7 +10,9 @@ import {
useRef,
} from 'react';
export default function useEnsuredForwardedRef<T>(forwardedRef: MutableRefObject<T>): MutableRefObject<T> {
export default function useEnsuredForwardedRef<T>(
forwardedRef: MutableRefObject<T>
): MutableRefObject<T> {
const ensuredRef = useRef(forwardedRef && forwardedRef.current);
useEffect(() => {

View File

@ -24,7 +24,11 @@ const isListenerType2 = (target: any): target is ListenerType2 => {
return !!target.on;
};
type AddEventListener<T> = T extends ListenerType1 ? T['addEventListener'] : T extends ListenerType2 ? T['on'] : never;
type AddEventListener<T> = T extends ListenerType1
? T['addEventListener']
: T extends ListenerType2
? T['on']
: never;
const useEvent = <T extends UseEventTarget>(
name: Parameters<AddEventListener<T>>[0],

View File

@ -2,7 +2,8 @@ import { useEffect } from 'react';
const useFavicon = (href: string) => {
useEffect(() => {
const link: HTMLLinkElement = document.querySelector("link[rel*='icon']") || document.createElement('link');
const link: HTMLLinkElement =
document.querySelector("link[rel*='icon']") || document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = href;

View File

@ -4,11 +4,17 @@ import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
import { noop, off, on } from './misc/util';
export interface FullScreenOptions {
video?: RefObject<HTMLVideoElement & { webkitEnterFullscreen?: () => void; webkitExitFullscreen?: () => void }>;
video?: RefObject<
HTMLVideoElement & { webkitEnterFullscreen?: () => void; webkitExitFullscreen?: () => void }
>;
onClose?: (error?: Error) => void;
}
const useFullscreen = (ref: RefObject<Element>, enabled: boolean, options: FullScreenOptions = {}): boolean => {
const useFullscreen = (
ref: RefObject<Element>,
enabled: boolean,
options: FullScreenOptions = {}
): boolean => {
const { video, onClose = noop } = options;
const [isFullscreen, setIsFullscreen] = useState(enabled);

View File

@ -1,7 +1,9 @@
import { useCallback, useRef } from 'react';
import useUpdate from './useUpdate';
const useGetSetState = <T extends object>(initialState: T = {} as T): [() => T, (patch: Partial<T>) => void] => {
const useGetSetState = <T extends object>(
initialState: T = {} as T
): [() => T, (patch: Partial<T>) => void] => {
if (process.env.NODE_ENV !== 'production') {
if (typeof initialState !== 'object') {
console.error('useGetSetState initial state must be an object.');

View File

@ -5,7 +5,11 @@ import { off, on } from './misc/util';
const defaultEvents = ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'];
const oneMinute = 60e3;
const useIdle = (ms: number = oneMinute, initialState: boolean = false, events: string[] = defaultEvents): boolean => {
const useIdle = (
ms: number = oneMinute,
initialState: boolean = false,
events: string[] = defaultEvents
): boolean => {
const [state, setState] = useState<boolean>(initialState);
useEffect(() => {

View File

@ -4,7 +4,10 @@ const useIntersection = (
ref: RefObject<HTMLElement>,
options: IntersectionObserverInit
): IntersectionObserverEntry | null => {
const [intersectionObserverEntry, setIntersectionObserverEntry] = useState<IntersectionObserverEntry | null>(null);
const [
intersectionObserverEntry,
setIntersectionObserverEntry,
] = useState<IntersectionObserverEntry | null>(null);
useEffect(() => {
if (ref.current && typeof IntersectionObserver === 'function') {

View File

@ -21,7 +21,12 @@ const createKeyPredicate = (keyFilter: KeyFilter): KeyPredicate =>
? () => true
: () => false;
const useKey = (key: KeyFilter, fn: Handler = noop, opts: UseKeyOptions = {}, deps: DependencyList = [key]) => {
const useKey = (
key: KeyFilter,
fn: Handler = noop,
opts: UseKeyOptions = {},
deps: DependencyList = [key]
) => {
const { event = 'keydown', target, options } = opts;
const useMemoHandler = useMemo(() => {
const predicate: KeyPredicate = createKeyPredicate(key);

View File

@ -117,7 +117,10 @@ function useList<T>(initialList: IHookStateInitAction<T[]> = []): [T[], ListActi
actions.set((curr: T[]) => curr.slice().sort(compareFn));
},
filter: <S extends T>(callbackFn: (value: T, index: number, array: T[]) => value is S, thisArg?: any) => {
filter: <S extends T>(
callbackFn: (value: T, index: number, array: T[]) => value is S,
thisArg?: any
) => {
actions.set((curr: T[]) => curr.slice().filter(callbackFn, thisArg));
},

View File

@ -23,7 +23,11 @@ const useLocalStorage = <T>(
throw new Error('useLocalStorage key may not be falsy');
}
const deserializer = options ? (options.raw ? (value) => value : options.deserializer) : JSON.parse;
const deserializer = options
? options.raw
? (value) => value
: options.deserializer
: JSON.parse;
// eslint-disable-next-line react-hooks/rules-of-hooks
const [state, setState] = useState<T | undefined>(() => {
@ -49,7 +53,8 @@ const useLocalStorage = <T>(
const set: Dispatch<SetStateAction<T | undefined>> = useCallback(
(valOrFunc) => {
try {
const newState = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc;
const newState =
typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc;
if (typeof newState === 'undefined') return;
let value: string;

View File

@ -1,7 +1,9 @@
import { RefObject, useEffect, useRef } from 'react';
import { isBrowser, off, on } from './misc/util';
export function getClosestBody(el: Element | HTMLElement | HTMLIFrameElement | null): HTMLElement | null {
export function getClosestBody(
el: Element | HTMLElement | HTMLIFrameElement | null
): HTMLElement | null {
if (!el) {
return null;
} else if (el.tagName === 'BODY') {
@ -32,7 +34,10 @@ export interface BodyInfoItem {
}
const isIosDevice =
isBrowser && window.navigator && window.navigator.platform && /iP(ad|hone|od)/.test(window.navigator.platform);
isBrowser &&
window.navigator &&
window.navigator.platform &&
/iP(ad|hone|od)/.test(window.navigator.platform);
const bodies: Map<HTMLElement, BodyInfoItem> = new Map();
@ -60,7 +65,10 @@ export default !doc
body.style.overflow = 'hidden';
}
} else {
bodies.set(body, { counter: bodyInfo.counter + 1, initialOverflow: bodyInfo.initialOverflow });
bodies.set(body, {
counter: bodyInfo.counter + 1,
initialOverflow: bodyInfo.initialOverflow,
});
}
};
@ -80,7 +88,10 @@ export default !doc
body.style.overflow = bodyInfo.initialOverflow;
}
} else {
bodies.set(body, { counter: bodyInfo.counter - 1, initialOverflow: bodyInfo.initialOverflow });
bodies.set(body, {
counter: bodyInfo.counter - 1,
initialOverflow: bodyInfo.initialOverflow,
});
}
}
};

View File

@ -7,7 +7,10 @@ export type UseMeasureRect = Pick<
'x' | 'y' | 'top' | 'left' | 'right' | 'bottom' | 'height' | 'width'
>;
export type UseMeasureRef<E extends HTMLElement = HTMLElement> = (element: E) => void;
export type UseMeasureResult<E extends HTMLElement = HTMLElement> = [UseMeasureRef<E>, UseMeasureRect];
export type UseMeasureResult<E extends HTMLElement = HTMLElement> = [
UseMeasureRef<E>,
UseMeasureRect
];
const defaultState: UseMeasureRect = {
x: 0,

View File

@ -2,7 +2,9 @@ import { useEffect, useState } from 'react';
import { isBrowser } from './misc/util';
const useMedia = (query: string, defaultState: boolean = false) => {
const [state, setState] = useState(isBrowser ? () => window.matchMedia(query).matches : defaultState);
const [state, setState] = useState(
isBrowser ? () => window.matchMedia(query).matches : defaultState
);
useEffect(() => {
let mounted = true;

View File

@ -13,7 +13,12 @@ const useMediaDevices = () => {
.then((devices) => {
if (mounted) {
setState({
devices: devices.map(({ deviceId, groupId, kind, label }) => ({ deviceId, groupId, kind, label })),
devices: devices.map(({ deviceId, groupId, kind, label }) => ({
deviceId,
groupId,
kind,
label,
})),
});
}
})
@ -34,4 +39,6 @@ const useMediaDevices = () => {
const useMediaDevicesMock = () => ({});
export default typeof navigator === 'object' && !!navigator.mediaDevices ? useMediaDevices : useMediaDevicesMock;
export default typeof navigator === 'object' && !!navigator.mediaDevices
? useMediaDevices
: useMediaDevicesMock;

View File

@ -11,9 +11,15 @@ export type UseMediatedStateReturn<S = any> = [S, Dispatch<SetStateAction<S>>];
export function useMediatedState<S = undefined>(
mediator: StateMediator<S | undefined>
): UseMediatedStateReturn<S | undefined>;
export function useMediatedState<S = any>(mediator: StateMediator<S>, initialState: S): UseMediatedStateReturn<S>;
export function useMediatedState<S = any>(
mediator: StateMediator<S>,
initialState: S
): UseMediatedStateReturn<S>;
export function useMediatedState<S = any>(mediator: StateMediator<S>, initialState?: S): UseMediatedStateReturn<S> {
export function useMediatedState<S = any>(
mediator: StateMediator<S>,
initialState?: S
): UseMediatedStateReturn<S> {
const mediatorFn = useRef(mediator);
const [state, setMediatedState] = useState<S>(initialState!);

View File

@ -15,7 +15,10 @@ type WrappedMethods<M> = {
[P in keyof M]: (...payload: any) => void;
};
const useMethods = <M, T>(createMethods: CreateMethods<M, T>, initialState: T): [T, WrappedMethods<M>] => {
const useMethods = <M, T>(
createMethods: CreateMethods<M, T>,
initialState: T
): [T, WrappedMethods<M>] => {
const reducer = useMemo<Reducer<T, Action>>(
() => (reducerState: T, action: Action) => {
return createMethods(reducerState)[action.type](...action.payload);

View File

@ -2,9 +2,15 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { StateValidator, UseStateValidatorReturn, ValidityState } from './useStateValidator';
export type MultiStateValidatorStates = any[] | { [p: string]: any } | { [p: number]: any };
export type MultiStateValidator<V extends ValidityState, S extends MultiStateValidatorStates> = StateValidator<V, S>;
export type MultiStateValidator<
V extends ValidityState,
S extends MultiStateValidatorStates
> = StateValidator<V, S>;
export function useMultiStateValidator<V extends ValidityState, S extends MultiStateValidatorStates>(
export function useMultiStateValidator<
V extends ValidityState,
S extends MultiStateValidatorStates
>(
states: S,
validator: MultiStateValidator<V, S>,
initialValidity: V = [undefined] as V

View File

@ -8,7 +8,15 @@ export interface INetworkInformation extends EventTarget {
readonly effectiveType: 'slow-2g' | '2g' | '3g' | '4g';
readonly rtt: number;
readonly saveData: boolean;
readonly type: 'bluetooth' | 'cellular' | 'ethernet' | 'none' | 'wifi' | 'wimax' | 'other' | 'unknown';
readonly type:
| 'bluetooth'
| 'cellular'
| 'ethernet'
| 'none'
| 'wifi'
| 'wimax'
| 'other'
| 'unknown';
onChange: (event: Event) => void;
}
@ -68,9 +76,11 @@ export interface IUseNetworkState {
}
const nav:
| (Navigator & Partial<Record<'connection' | 'mozConnection' | 'webkitConnection', INetworkInformation>>)
| (Navigator &
Partial<Record<'connection' | 'mozConnection' | 'webkitConnection', INetworkInformation>>)
| undefined = navigator;
const conn: INetworkInformation | undefined = nav && (nav.connection || nav.mozConnection || nav.webkitConnection);
const conn: INetworkInformation | undefined =
nav && (nav.connection || nav.mozConnection || nav.webkitConnection);
function getConnectionState(previousState?: IUseNetworkState): IUseNetworkState {
const online = nav?.onLine;
@ -89,7 +99,9 @@ function getConnectionState(previousState?: IUseNetworkState): IUseNetworkState
};
}
export default function useNetworkState(initialState?: IHookStateInitAction<IUseNetworkState>): IUseNetworkState {
export default function useNetworkState(
initialState?: IHookStateInitAction<IUseNetworkState>
): IUseNetworkState {
const [state, setState] = useState(initialState ?? getConnectionState);
useEffect(() => {

View File

@ -5,7 +5,10 @@ export type Predicate<T> = (prev: T | undefined, next: T) => boolean;
const strictEquals = <T>(prev: T | undefined, next: T) => prev === next;
export default function usePreviousDistinct<T>(value: T, compare: Predicate<T> = strictEquals): T | undefined {
export default function usePreviousDistinct<T>(
value: T,
compare: Predicate<T> = strictEquals
): T | undefined {
const prevRef = useRef<T>();
const curRef = useRef<T>(value);
const isFirstMount = useFirstMountState();

View File

@ -2,7 +2,10 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
export type RafLoopReturns = [() => void, () => void, () => boolean];
export default function useRafLoop(callback: FrameRequestCallback, initiallyActive = true): RafLoopReturns {
export default function useRafLoop(
callback: FrameRequestCallback,
initiallyActive = true
): RafLoopReturns {
const raf = useRef<number | null>(null);
const rafActivity = useRef<boolean>(false);
const rafCallback = useRef(callback);

View File

@ -28,7 +28,9 @@ export interface ScratchSensorState {
elY?: number;
}
const useScratch = (params: ScratchSensorParams = {}): [(el: HTMLElement | null) => void, ScratchSensorState] => {
const useScratch = (
params: ScratchSensorParams = {}
): [(el: HTMLElement | null) => void, ScratchSensorState] => {
const { disabled } = params;
const paramsRef = useLatest(params);
const [state, setState] = useState<ScratchSensorState>({ isScratching: false });
@ -155,7 +157,10 @@ const useScratch = (params: ScratchSensorParams = {}): [(el: HTMLElement | null)
};
export interface ScratchSensorProps extends ScratchSensorParams {
children: (state: ScratchSensorState, ref: (el: HTMLElement | null) => void) => React.ReactElement<any>;
children: (
state: ScratchSensorState,
ref: (el: HTMLElement | null) => void
) => React.ReactElement<any>;
}
export const ScratchSensor: FC<ScratchSensorProps> = (props) => {

View File

@ -1,7 +1,11 @@
import { useEffect, useState } from 'react';
import { isBrowser } from './misc/util';
const useSessionStorage = <T>(key: string, initialValue?: T, raw?: boolean): [T, (value: T) => void] => {
const useSessionStorage = <T>(
key: string,
initialValue?: T,
raw?: boolean
): [T, (value: T) => void] => {
if (!isBrowser) {
return [initialValue as T, () => {}];
}

View File

@ -16,7 +16,8 @@ const useSet = <K>(initialSet = new Set<K>()): [Set<K>, Actions<K>] => {
const stableActions = useMemo<StableActions<K>>(() => {
const add = (item: K) => setSet((prevSet) => new Set([...Array.from(prevSet), item]));
const remove = (item: K) => setSet((prevSet) => new Set(Array.from(prevSet).filter((i) => i !== item)));
const remove = (item: K) =>
setSet((prevSet) => new Set(Array.from(prevSet).filter((i) => i !== item)));
const toggle = (item: K) =>
setSet((prevSet) =>
prevSet.has(item)

View File

@ -6,7 +6,9 @@ const useSetState = <T extends object>(
const [state, set] = useState<T>(initialState);
const setState = useCallback(
(patch) => {
set((prevState) => Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch));
set((prevState) =>
Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch)
);
},
[set]
);

View File

@ -9,7 +9,9 @@ const shallowEqualDepsList = (prevDeps: DependencyList, nextDeps: DependencyList
const useShallowCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
if (process.env.NODE_ENV !== 'production') {
if (!(deps instanceof Array) || !deps.length) {
console.warn('`useShallowCompareEffect` should not be used with no dependencies. Use React.useEffect instead.');
console.warn(
'`useShallowCompareEffect` should not be used with no dependencies. Use React.useEffect instead.'
);
}
if (deps.every(isPrimitive)) {

View File

@ -17,7 +17,10 @@ const useSize = (
{ width = Infinity, height = Infinity }: Partial<State> = {}
): [React.ReactElement<any>, State] => {
if (!isBrowser) {
return [typeof element === 'function' ? element({ width, height }) : element, { width, height }];
return [
typeof element === 'function' ? element({ width, height }) : element,
{ width, height },
];
}
// eslint-disable-next-line react-hooks/rules-of-hooks

View File

@ -20,7 +20,8 @@ export interface SpeechOptions {
volume?: number;
}
const voices = isBrowser && typeof window.speechSynthesis === 'object' ? window.speechSynthesis.getVoices() : [];
const voices =
isBrowser && typeof window.speechSynthesis === 'object' ? window.speechSynthesis.getVoices() : [];
const useSpeech = (text: string, opts: SpeechOptions = {}): SpeechState => {
const [state, setState] = useSetState<SpeechState>({

View File

@ -42,7 +42,10 @@ export default function useStateList<T>(stateSet: T[] = []): UseStateListReturn<
// it gives the ability to travel through the left and right borders.
// 4ex: if list contains 5 elements, attempt to set index 9 will bring use to 5th element
// in case of negative index it will start counting from the right, so -17 will bring us to 4th element
index.current = newIndex >= 0 ? newIndex % stateSet.length : stateSet.length + (newIndex % stateSet.length);
index.current =
newIndex >= 0
? newIndex % stateSet.length
: stateSet.length + (newIndex % stateSet.length);
update();
},
setState: (state: T) => {

View File

@ -104,7 +104,10 @@ export function useStateWithHistory<S, I extends S>(
}
innerSetState(() => {
historyPosition.current = Math.min(historyPosition.current + amount, history.current.length - 1);
historyPosition.current = Math.min(
historyPosition.current + amount,
history.current.length - 1
);
return history.current[historyPosition.current];
});

View File

@ -1,6 +1,7 @@
import { Reducer, useReducer } from 'react';
const toggleReducer = (state: boolean, nextValue?: any) => (typeof nextValue === 'boolean' ? nextValue : !state);
const toggleReducer = (state: boolean, nextValue?: any) =>
typeof nextValue === 'boolean' ? nextValue : !state;
const useToggle = (initialValue: boolean): [boolean, (nextValue?: any) => void] => {
return useReducer<Reducer<boolean, any>>(toggleReducer, initialValue);

View File

@ -5,7 +5,11 @@ export type VibrationPattern = number | number[];
const isVibrationApiSupported = typeof navigator === 'object' && 'vibrate' in navigator;
function useVibrate(enabled: boolean = true, pattern: VibrationPattern = [1000, 1000], loop: boolean = true): void {
function useVibrate(
enabled: boolean = true,
pattern: VibrationPattern = [1000, 1000],
loop: boolean = true
): void {
useEffect(() => {
let interval;
@ -13,7 +17,8 @@ function useVibrate(enabled: boolean = true, pattern: VibrationPattern = [1000,
navigator.vibrate(pattern);
if (loop) {
const duration = pattern instanceof Array ? pattern.reduce((a, b) => a + b) : (pattern as number);
const duration =
pattern instanceof Array ? pattern.reduce((a, b) => a + b) : (pattern as number);
interval = setInterval(() => {
navigator.vibrate(pattern);

View File

@ -27,8 +27,10 @@ const Demo = () => {
<div>
<strong>Battery sensor</strong>:&nbsp;&nbsp; <span>supported</span> <br />
<strong>Battery state</strong>: <span>fetched</span> <br />
<strong>Charge level</strong>:&nbsp;&nbsp; <span>{(batteryState.level * 100).toFixed(0)}%</span> <br />
<strong>Charging</strong>:&nbsp;&nbsp; <span>{batteryState.charging ? 'yes' : 'no'}</span> <br />
<strong>Charge level</strong>:&nbsp;&nbsp;{' '}
<span>{(batteryState.level * 100).toFixed(0)}%</span> <br />
<strong>Charging</strong>:&nbsp;&nbsp; <span>{batteryState.charging ? 'yes' : 'no'}</span>{' '}
<br />
<strong>Charging time</strong>:&nbsp;&nbsp;
<span>{batteryState.chargingTime ? batteryState.chargingTime : 'finished'}</span> <br />
<strong>Discharging time</strong>:&nbsp;&nbsp; <span>{batteryState.dischargingTime}</span>

View File

@ -21,7 +21,8 @@ const Demo = () => {
return (
<CenterStory>
<p>
Press some keys on your keyboard, <code style={{ color: 'tomato' }}>r</code> key resets the list
Press some keys on your keyboard, <code style={{ color: 'tomato' }}>r</code> key resets the
list
</p>
<pre>{JSON.stringify(list, null, 4)}</pre>
</CenterStory>

View File

@ -32,8 +32,7 @@ const Demo = () => {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
}}>
<video
ref={videoRef}
style={{ width: '70%' }}

View File

@ -10,5 +10,5 @@ const Demo = () => {
};
storiesOf('Sensors/useGeolocation', module)
.add('Docs', () => <ShowDocs md={require('../docs/useGeolocation.md')} />)
.add('Docs', () => <ShowDocs md={require('../docs/useGeoLocation.md')} />)
.add('Demo', () => <Demo />);

View File

@ -9,7 +9,12 @@ const Demo = () => {
return (
<div>
Idle delay ms: <input type="number" value={idleDelay} onChange={({ target }) => setIdleDelay(+target.value)} />
Idle delay ms:{' '}
<input
type="number"
value={idleDelay}
onChange={({ target }) => setIdleDelay(+target.value)}
/>
<div>User is idle: {isIdle ? 'Yes' : 'No'}</div>
</div>
);

View File

@ -28,8 +28,7 @@ const Demo = () => {
height: '400px',
backgroundColor: 'whitesmoke',
overflow: 'scroll',
}}
>
}}>
Scroll me
<Spacer />
<div
@ -39,8 +38,7 @@ const Demo = () => {
height: '100px',
padding: '20px',
backgroundColor: 'palegreen',
}}
>
}}>
{intersection && intersection.intersectionRatio < 1 ? 'Obscured' : 'Fully in view'}
</div>
<Spacer />

View File

@ -7,8 +7,8 @@ import ShowDocs from './util/ShowDocs';
const Demo = () => {
const [count, setCount] = React.useState(0);
const increment = () => setCount(currentCount => ++currentCount);
const decrement = () => setCount(currentCount => --currentCount);
const increment = () => setCount((currentCount) => ++currentCount);
const decrement = () => setCount((currentCount) => --currentCount);
const reset = () => setCount(() => 0);
useKey(']', increment);
@ -19,7 +19,8 @@ const Demo = () => {
<CenterStory>
<style dangerouslySetInnerHTML={{ __html: `code {color: red}` }} />
<p>
Try pressing <code>[</code>, <code>]</code>, and <code>r</code> to see the count incremented and decremented.
Try pressing <code>[</code>, <code>]</code>, and <code>r</code> to see the count incremented
and decremented.
</p>
<p>Count: {count}</p>
</CenterStory>
@ -28,7 +29,7 @@ const Demo = () => {
const CounterDemo = () => {
const [count, setCount] = React.useState(0);
const increment = () => setCount(currentCount => ++currentCount);
const increment = () => setCount((currentCount) => ++currentCount);
useKey('ArrowUp', increment);
return <div>Press arrow up: {count}</div>;

View File

@ -7,8 +7,9 @@ import ShowDocs from './util/ShowDocs';
const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
const Demo = () => {
const states = [];
const states: boolean[] = [];
for (const key of keys) {
// eslint-disable-next-line react-hooks/rules-of-hooks
states.push(useKeyPress(key)[0]);
}
@ -17,7 +18,10 @@ const Demo = () => {
<div style={{ textAlign: 'center' }}>
Try pressing numbers
<br />
{states.reduce((s, pressed, index) => s + (pressed ? (s ? ' + ' : '') + keys[index] : ''), '')}
{states.reduce(
(s, pressed, index) => s + (pressed ? (s ? ' + ' : '') + keys[index] : ''),
''
)}
</div>
</CenterStory>
);

View File

@ -8,11 +8,11 @@ const Demo = () => {
const increment = () => {
console.log('INCREMENT');
setCount(currentCount => ++currentCount);
setCount((currentCount) => ++currentCount);
};
const decrement = () => {
console.log('DECREMENT');
setCount(currentCount => --currentCount);
setCount((currentCount) => --currentCount);
};
const reset = () => setCount(() => 0);
@ -24,7 +24,8 @@ const Demo = () => {
<div>
<style dangerouslySetInnerHTML={{ __html: `code {color: red}` }} />
<p>
Try pressing <code>[</code>, <code>]</code>, and <code>r</code> to see the count incremented and decremented.
Try pressing <code>[</code>, <code>]</code>, and <code>r</code> to see the count incremented
and decremented.
</p>
<p>Count: {count}</p>
</div>
@ -36,11 +37,11 @@ const DemoKeyboardJs = () => {
const increment = () => {
console.log('INCREMENT');
setCount(currentCount => ++currentCount);
setCount((currentCount) => ++currentCount);
};
const decrement = () => {
console.log('DECREMENT');
setCount(currentCount => --currentCount);
setCount((currentCount) => --currentCount);
};
const reset = () => setCount(() => 0);
@ -52,8 +53,8 @@ const DemoKeyboardJs = () => {
<div>
<style dangerouslySetInnerHTML={{ __html: `code {color: red}` }} />
<p>
Try pressing <code>q + [</code>, <code>q + ]</code>, and <code>q + r</code> to see the count incremented and
decremented.
Try pressing <code>q + [</code>, <code>q + ]</code>, and <code>q + r</code> to see the count
incremented and decremented.
</p>
<p>Count: {count}</p>
</div>

View File

@ -12,7 +12,10 @@ const Demo = ({ combo }) => {
<CenterStory>
<div style={{ textAlign: 'center' }}>
Press{' '}
<code style={{ color: 'red', background: '#f6f6f6', padding: '3px 6px', borderRadius: '3px' }}>{combo}</code>{' '}
<code
style={{ color: 'red', background: '#f6f6f6', padding: '3px 6px', borderRadius: '3px' }}>
{combo}
</code>{' '}
combo
<br />
<br />

View File

@ -4,13 +4,10 @@ import { useList } from '../src';
import ShowDocs from './util/ShowDocs';
const Demo = () => {
const [list, { set, push, updateAt, insertAt, update, updateFirst, sort, filter, removeAt, clear, reset }] = useList([
1,
2,
3,
4,
5,
]);
const [
list,
{ set, push, updateAt, insertAt, update, updateFirst, sort, filter, removeAt, clear, reset },
] = useList([1, 2, 3, 4, 5]);
return (
<div>
@ -64,11 +61,17 @@ const UpsertDemo = () => {
<div style={{ display: 'inline-flex', flexDirection: 'column' }}>
{list.map((item, index) => (
<div key={item.id}>
<input value={item.text} onChange={(e) => upsert(upsertPredicate, { ...item, text: e.target.value })} />
<input
value={item.text}
onChange={(e) => upsert(upsertPredicate, { ...item, text: e.target.value })}
/>
<button onClick={() => removeAt(index)}>Remove</button>
</div>
))}
<button onClick={() => upsert(upsertPredicate, { id: (list.length + 1).toString(), text: '' })}>Add item</button>
<button
onClick={() => upsert(upsertPredicate, { id: (list.length + 1).toString(), text: '' })}>
Add item
</button>
<button onClick={() => reset()}>Reset</button>
</div>
);

View File

@ -3,7 +3,7 @@ import * as React from 'react';
import { useLocation } from '../src';
import ShowDocs from './util/ShowDocs';
const go = (page) => history.pushState({}, '', page);
const go = (page) => window.history.pushState({}, '', page);
const Demo = () => {
const state = useLocation();

View File

@ -44,7 +44,9 @@ const IframeComponent = () => {
<button onClick={() => toggleMainLocked()} style={{ position: 'fixed', left: 0, top: 0 }}>
{mainLocked ? 'Unlock' : 'Lock'} main window scroll
</button>
<button onClick={() => toggleIframeLocked()} style={{ position: 'fixed', left: 0, top: 64 }}>
<button
onClick={() => toggleIframeLocked()}
style={{ position: 'fixed', left: 0, top: 64 }}>
{iframeLocked ? 'Unlock' : 'Lock'} iframe window scroll
</button>
</div>

View File

@ -19,8 +19,7 @@ const Demo: React.FC<any> = () => {
width: '400px',
height: '400px',
backgroundColor: 'whitesmoke',
}}
>
}}>
<span
style={{
position: 'absolute',
@ -28,8 +27,7 @@ const Demo: React.FC<any> = () => {
top: `${state.elY}px`,
pointerEvents: 'none',
transform: 'scale(4)',
}}
>
}}>
🐭
</span>
</div>

View File

@ -20,8 +20,7 @@ const Demo: React.FC<any> = ({ whenHovered, bound }) => {
width: '400px',
height: '400px',
backgroundColor: 'whitesmoke',
}}
>
}}>
<span
style={{
position: 'absolute',
@ -29,8 +28,7 @@ const Demo: React.FC<any> = ({ whenHovered, bound }) => {
top: `${state.elY}px`,
pointerEvents: 'none',
transform: 'scale(4)',
}}
>
}}>
🐭
</span>
</div>

View File

@ -41,7 +41,9 @@ const Demo = () => {
setState3((ev.target.value as unknown) as number);
}}
/>
{isValid !== undefined && <span style={{ marginLeft: 24 }}>{isValid ? 'Valid!' : 'Invalid'}</span>}
{isValid !== undefined && (
<span style={{ marginLeft: 24 }}>{isValid ? 'Valid!' : 'Invalid'}</span>
)}
</div>
);
};

View File

@ -9,12 +9,14 @@ const Demo = () => {
useEffect(() => {
console.log(state);
}, [state])
}, [state]);
return <div>
<div>Since JSON do not output `undefined` fields look the console to see whole the state</div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>;
return (
<div>
<div>Since JSON do not output `undefined` fields look the console to see whole the state</div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
);
};
storiesOf('Sensors/useNetworkState', module)

View File

@ -22,8 +22,7 @@ const Demo = () => {
onClick={() => {
isActive() ? loopStop() : loopStart();
update();
}}
>
}}>
{isActive() ? 'STOP' : 'START'}
</button>
</div>

View File

@ -18,8 +18,7 @@ const Demo = () => {
height: '400px',
backgroundColor: 'whitesmoke',
overflow: 'scroll',
}}
>
}}>
<div style={{ width: '2000px', height: '2000px' }}>Scroll me</div>
</div>
</>

View File

@ -8,7 +8,9 @@ const Demo = () => {
return (
<div>
{sbw === undefined ? `DOM is not ready yet, SBW detection delayed` : `Browser's scrollbar width is ${sbw}px`}
{sbw === undefined
? `DOM is not ready yet, SBW detection delayed`
: `Browser's scrollbar width is ${sbw}px`}
</div>
);
};

View File

@ -18,8 +18,7 @@ const Demo = () => {
height: '400px',
backgroundColor: 'whitesmoke',
overflow: 'scroll',
}}
>
}}>
<div style={{ width: '2000px', height: '2000px' }}>Scroll me</div>
</div>
</>

View File

@ -10,17 +10,21 @@ const Demo = () => {
<div>
<div>edit: {edit || '🤷‍♂️'}</div>
<div>
<button onClick={() => history.pushState({}, '', location.pathname + '?edit=123')}>
<button
onClick={() => window.history.pushState({}, '', window.location.pathname + '?edit=123')}>
Edit post 123 (?edit=123)
</button>
</div>
<div>
<button onClick={() => history.pushState({}, '', location.pathname + '?edit=999')}>
<button
onClick={() => window.history.pushState({}, '', window.location.pathname + '?edit=999')}>
Edit post 999 (?edit=999)
</button>
</div>
<div>
<button onClick={() => history.pushState({}, '', location.pathname)}>Close modal</button>
<button onClick={() => window.history.pushState({}, '', window.location.pathname)}>
Close modal
</button>
</div>
</div>
);

View File

@ -16,8 +16,7 @@ const Demo = () => {
setState((prevState) => ({
count: prevState.count === undefined ? 0 : prevState.count + 1,
}));
}}
>
}}>
increment
</button>
</div>

View File

@ -15,7 +15,13 @@ const Demo = () => {
<div>
<div ref={ref} style={{ position: 'relative', background: 'yellow', padding: 4 }}>
<p style={{ margin: 0, textAlign: 'center' }}>Slide me</p>
<div style={{ position: 'absolute', top: 0, left: 100 * state.value + '%', transform: 'scale(2)' }}>
<div
style={{
position: 'absolute',
top: 0,
left: 100 * state.value + '%',
transform: 'scale(2)',
}}>
{state.isSliding ? '🏂' : '🎿'}
</div>
</div>
@ -30,9 +36,17 @@ const DemoVertical = () => {
return (
<div>
<div ref={ref} style={{ position: 'relative', background: 'yellow', padding: 4, width: 30, height: 400 }}>
<div
ref={ref}
style={{ position: 'relative', background: 'yellow', padding: 4, width: 30, height: 400 }}>
<p style={{ margin: 0, textAlign: 'center' }}>Slide me</p>
<div style={{ position: 'absolute', left: 0, top: 100 * state.value + '%', transform: 'scale(2)' }}>
<div
style={{
position: 'absolute',
left: 0,
top: 100 * state.value + '%',
transform: 'scale(2)',
}}>
{state.isSliding ? '🏂' : '🎿'}
</div>
</div>

View File

@ -21,7 +21,9 @@ const Demo = () => {
<button onClick={() => next()}>next</button>
<br />
<input type="text" ref={indexInput} style={{ width: 120 }} />
<button onClick={() => setStateAt((indexInput.current!.value as unknown) as number)}>set state by index</button>
<button onClick={() => setStateAt((indexInput.current!.value as unknown) as number)}>
set state by index
</button>
<br />
<input type="text" ref={stateInput} style={{ width: 120 }} />
<button onClick={() => setState(stateInput.current!.value)}> set state by value</button>

View File

@ -3,7 +3,8 @@ import * as React from 'react';
import useStateValidator from '../src/useStateValidator';
import ShowDocs from './util/ShowDocs';
const DemoStateValidator = (s) => [s === '' ? undefined : (s * 1) % 2 === 0] as [boolean | undefined];
const DemoStateValidator = (s) =>
[s === '' ? undefined : (s * 1) % 2 === 0] as [boolean | undefined];
const Demo = () => {
const [state, setState] = React.useState<number>(0);
const [[isValid]] = useStateValidator(state, DemoStateValidator);

View File

@ -24,7 +24,7 @@ const Demo = () => {
return;
}
history.back(stepSize);
window.history.back(stepSize);
},
[history, stepSize]
);
@ -35,7 +35,7 @@ const Demo = () => {
return;
}
history.forward(stepSize);
window.history.forward(stepSize);
},
[history, stepSize]
);
@ -60,10 +60,12 @@ const Demo = () => {
Current state: <span>{state}</span>
</div>
<div style={{ marginTop: 8 }}>
<button onClick={handleBackClick} disabled={!history.position}>
<button onClick={handleBackClick} disabled={!window.history.position}>
&lt; Back
</button>
<button onClick={handleForwardClick} disabled={history.position >= history.history.length - 1}>
<button
onClick={handleForwardClick}
disabled={window.history.position >= window.history.window.history.length - 1}>
Forward &gt;
</button>
&nbsp; Step size:&nbsp;
@ -74,7 +76,9 @@ const Demo = () => {
<div>Current history</div>
<div
dangerouslySetInnerHTML={{
__html: JSON.stringify(history.history, null, 2).replace(/\n/g, '<br/>').replace(/ /g, '&nbsp;'),
__html: JSON.stringify(window.history.history, null, 2)
.replace(/\n/g, '<br/>')
.replace(/ /g, '&nbsp;'),
}}
/>
</div>
@ -83,5 +87,5 @@ const Demo = () => {
};
storiesOf('State/useStateWithHistory', module)
.add('Docs', () => <ShowDocs md={require('../docs/useStateWithHistory.md')} />)
.add('Docs', () => <ShowDocs md={require('../docs/useStateWithwindow.history.md')} />)
.add('Demo', () => <Demo />);

View File

@ -27,9 +27,14 @@ const Demo = () => {
return (
<div>
<div>{readyState !== null ? 'Function will be called in 5 seconds' : 'Timer cancelled'}</div>
<button onClick={cancelButtonClick}> {readyState === false ? 'cancel' : 'restart'} timeout</button>
<button onClick={cancelButtonClick}>
{' '}
{readyState === false ? 'cancel' : 'restart'} timeout
</button>
<br />
<div>Function state: {readyState === false ? 'Pending' : readyState ? 'Called' : 'Cancelled'}</div>
<div>
Function state: {readyState === false ? 'Pending' : readyState ? 'Called' : 'Cancelled'}
</div>
<div>{state}</div>
</div>
);

View File

@ -8,8 +8,8 @@ const Demo = () => {
return (
<div>
<code>useUnmount()</code> hook can be used to perform side-effects when component unmounts. This component will
alert you when it is un-mounted.
<code>useUnmount()</code> hook can be used to perform side-effects when component unmounts.
This component will alert you when it is un-mounted.
</div>
);
};

View File

@ -27,7 +27,9 @@ const Demo = () => {
<button onClick={() => remove(index)}>Remove</button>
</div>
))}
<button onClick={() => upsert({ id: (list.length + 1).toString(), text: '' })}>Add item</button>
<button onClick={() => upsert({ id: (list.length + 1).toString(), text: '' })}>
Add item
</button>
<button onClick={() => set([])}>Reset</button>
</div>
);

View File

@ -11,15 +11,13 @@ const Demo = () => {
style={{
width: '200vw',
height: '200vh',
}}
>
}}>
<div
style={{
position: 'fixed',
left: 0,
right: 0,
}}
>
}}>
<div>x: {x}</div>
<div>y: {y}</div>
</div>

View File

@ -8,8 +8,7 @@ export const CenterStory = ({ children }) => (
alignItems: 'center',
maxWidth: '400px',
margin: '40px auto',
}}
>
}}>
<div style={{ width: '100%' }}>{children}</div>
</div>
);

View File

@ -2,7 +2,7 @@ import * as React from 'react';
const h = React.createElement;
const ShowDocs = props => {
const ShowDocs = (props) => {
return h(
'div',
{},

View File

@ -5,7 +5,7 @@ const useBreakpointA = createBreakpoint();
const useBreakpointB = createBreakpoint({ mobileM: 350, laptop: 1024, tablet: 768 });
const originalInnerWidth = window.innerWidth;
const changeInnerWidth = value =>
const changeInnerWidth = (value) =>
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value });
const revert = () => changeInnerWidth(originalInnerWidth);

View File

@ -16,10 +16,13 @@ describe('when using created memo hook', () => {
useMemoGetDouble = createMemo(getDouble);
});
it.each([[1], [3], [5]])('should return same result as original function for argument %d', (val: number) => {
const { result } = renderHook(() => useMemoGetDouble(val));
expect(result.current).toBe(getDouble(val));
});
it.each([[1], [3], [5]])(
'should return same result as original function for argument %d',
(val: number) => {
const { result } = renderHook(() => useMemoGetDouble(val));
expect(result.current).toBe(getDouble(val));
}
);
it('should NOT call original function for same arguments', () => {
let initialValue = 5;

View File

@ -34,7 +34,7 @@ describe('when using created reducer hook', () => {
// Action creator to increment count, wait a second and then reset
const addAndReset = () => {
return dispatch => {
return (dispatch) => {
dispatch({ type: 'increment' });
setTimeout(() => {

View File

@ -26,12 +26,16 @@ describe('when using created hook', () => {
it('should throw out of a provider', () => {
const [useSharedNumber] = createReducerContext(reducer, 0);
const { result } = renderHook(() => useSharedNumber());
expect(result.error).toEqual(new Error('useReducerContext must be used inside a ReducerProvider.'));
expect(result.error).toEqual(
new Error('useReducerContext must be used inside a ReducerProvider.')
);
});
const setUp = () => {
const [useSharedNumber, SharedNumberProvider] = createReducerContext(reducer, 0);
const wrapper: React.FC = ({ children }) => <SharedNumberProvider>{children}</SharedNumberProvider>;
const wrapper: React.FC = ({ children }) => (
<SharedNumberProvider>{children}</SharedNumberProvider>
);
return renderHook(() => useSharedNumber(), { wrapper });
};
@ -81,11 +85,15 @@ describe('when using among multiple components', () => {
</SharedNumberProvider>
);
expect(baseElement.innerHTML).toBe('<div><p>0</p><p>0</p><button type="button">INCREMENT</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>0</p><p>0</p><button type="button">INCREMENT</button></div>'
);
fireEvent.click(getByText('INCREMENT'));
expect(baseElement.innerHTML).toBe('<div><p>1</p><p>1</p><button type="button">INCREMENT</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>1</p><p>1</p><button type="button">INCREMENT</button></div>'
);
});
it('should be in update independently when under different providers', () => {
@ -101,11 +109,15 @@ describe('when using among multiple components', () => {
</>
);
expect(baseElement.innerHTML).toBe('<div><p>0</p><p>0</p><button type="button">INCREMENT</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>0</p><p>0</p><button type="button">INCREMENT</button></div>'
);
fireEvent.click(getByText('INCREMENT'));
expect(baseElement.innerHTML).toBe('<div><p>0</p><p>1</p><button type="button">INCREMENT</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>0</p><p>1</p><button type="button">INCREMENT</button></div>'
);
});
it('should not update component that do not use the state context', () => {
@ -125,11 +137,15 @@ describe('when using among multiple components', () => {
</>
);
expect(baseElement.innerHTML).toBe('<div><p>static</p><p>0</p><button type="button">INCREMENT</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>static</p><p>0</p><button type="button">INCREMENT</button></div>'
);
fireEvent.click(getByText('INCREMENT'));
expect(baseElement.innerHTML).toBe('<div><p>static</p><p>1</p><button type="button">INCREMENT</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>static</p><p>1</p><button type="button">INCREMENT</button></div>'
);
expect(renderCount).toBe(1);
});

View File

@ -68,11 +68,15 @@ describe('when using among multiple components', () => {
</SharedTextProvider>
);
expect(baseElement.innerHTML).toBe('<div><p>init</p><p>init</p><button type="button">UPDATE</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>init</p><p>init</p><button type="button">UPDATE</button></div>'
);
fireEvent.click(getByText('UPDATE'));
expect(baseElement.innerHTML).toBe('<div><p>changed</p><p>changed</p><button type="button">UPDATE</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>changed</p><p>changed</p><button type="button">UPDATE</button></div>'
);
});
it('should be in update independently when under different providers', () => {
@ -88,11 +92,15 @@ describe('when using among multiple components', () => {
</>
);
expect(baseElement.innerHTML).toBe('<div><p>init</p><p>init</p><button type="button">UPDATE</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>init</p><p>init</p><button type="button">UPDATE</button></div>'
);
fireEvent.click(getByText('UPDATE'));
expect(baseElement.innerHTML).toBe('<div><p>init</p><p>changed</p><button type="button">UPDATE</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>init</p><p>changed</p><button type="button">UPDATE</button></div>'
);
});
it('should not update component that do not use the state context', () => {
@ -112,11 +120,15 @@ describe('when using among multiple components', () => {
</>
);
expect(baseElement.innerHTML).toBe('<div><p>static</p><p>init</p><button type="button">UPDATE</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>static</p><p>init</p><button type="button">UPDATE</button></div>'
);
fireEvent.click(getByText('UPDATE'));
expect(baseElement.innerHTML).toBe('<div><p>static</p><p>changed</p><button type="button">UPDATE</button></div>');
expect(baseElement.innerHTML).toBe(
'<div><p>static</p><p>changed</p><button type="button">UPDATE</button></div>'
);
expect(renderCount).toBe(1);
});

View File

@ -25,8 +25,7 @@ describe('resolveHookState', () => {
});
it('should not pass 2nd parameter to function if it not awaited', () => {
const spy = jest.fn(() => {
});
const spy = jest.fn(() => {});
/* @ts-expect-error */
resolveHookState(spy, 123);
expect(spy).toHaveBeenCalled();

View File

@ -1,4 +1,3 @@
import { renderHook } from '@testing-library/react-hooks';
import { useCallback } from 'react';
import useAsync from '../src/useAsync';
@ -13,7 +12,7 @@ describe('useAsync', () => {
let callCount = 0;
const resolver = async () => {
return new Promise(resolve => {
return new Promise((resolve) => {
callCount++;
const wait = setTimeout(() => {
@ -107,7 +106,7 @@ describe('useAsync', () => {
return 'new value';
};
beforeEach(done => {
beforeEach((done) => {
callCount = 0;
hook = renderHook(({ fn }) => useAsync(fn, [fn]), {
@ -138,12 +137,12 @@ describe('useAsync', () => {
let callCount = 0;
let hook;
const staticFunction = async counter => {
const staticFunction = async (counter) => {
callCount++;
return `counter is ${counter} and callCount is ${callCount}`;
};
beforeEach(done => {
beforeEach((done) => {
callCount = 0;
hook = renderHook(
({ fn, counter }) => {

View File

@ -23,9 +23,12 @@ describe('useAsyncFn', () => {
beforeEach(() => {
// NOTE: renderHook isn't good at inferring array types
hook = renderHook<{ fn: AdderFn }, [AsyncState<number>, AdderFn]>(({ fn }) => useAsyncFn(fn), {
initialProps: { fn: adder },
});
hook = renderHook<{ fn: AdderFn }, [AsyncState<number>, AdderFn]>(
({ fn }) => useAsyncFn(fn),
{
initialProps: { fn: adder },
}
);
});
it('awaits the result', async () => {
@ -57,11 +60,14 @@ describe('useAsyncFn', () => {
beforeEach(() => {
// NOTE: renderHook isn't good at inferring array types
hook = renderHook<{ fn: AdderFn }, [AsyncState<number>, AdderFn]>(({ fn }) => useAsyncFn(fn), {
initialProps: {
fn: adder,
},
});
hook = renderHook<{ fn: AdderFn }, [AsyncState<number>, AdderFn]>(
({ fn }) => useAsyncFn(fn),
{
initialProps: {
fn: adder,
},
}
);
});
it('initially does not have a value', () => {
@ -98,18 +104,22 @@ describe('useAsyncFn', () => {
it('should only consider last call and discard previous ones', async () => {
const queuedPromises: { id: number; resolve: () => void }[] = [];
const delayedFunction1 = () => {
return new Promise<number>(resolve => queuedPromises.push({ id: 1, resolve: () => resolve(1) }));
return new Promise<number>((resolve) =>
queuedPromises.push({ id: 1, resolve: () => resolve(1) })
);
};
const delayedFunction2 = () => {
return new Promise<number>(resolve => queuedPromises.push({ id: 2, resolve: () => resolve(2) }));
return new Promise<number>((resolve) =>
queuedPromises.push({ id: 2, resolve: () => resolve(2) })
);
};
const hook = renderHook<{ fn: () => Promise<number> }, [AsyncState<number>, () => Promise<number>]>(
({ fn }) => useAsyncFn(fn, [fn]),
{
initialProps: { fn: delayedFunction1 },
}
);
const hook = renderHook<
{ fn: () => Promise<number> },
[AsyncState<number>, () => Promise<number>]
>(({ fn }) => useAsyncFn(fn, [fn]), {
initialProps: { fn: delayedFunction1 },
});
act(() => {
hook.result.current[1](); // invoke 1st callback
});
@ -131,12 +141,12 @@ describe('useAsyncFn', () => {
const fetch = async () => 'new state';
const initialState = { loading: false, value: 'init state' };
const hook = renderHook<{ fn: () => Promise<string> }, [AsyncState<string>, () => Promise<string>]>(
({ fn }) => useAsyncFn(fn, [fn], initialState),
{
initialProps: { fn: fetch },
}
);
const hook = renderHook<
{ fn: () => Promise<string> },
[AsyncState<string>, () => Promise<string>]
>(({ fn }) => useAsyncFn(fn, [fn], initialState), {
initialProps: { fn: fetch },
});
const [state, callback] = hook.result.current;
expect(state.loading).toBe(false);

View File

@ -4,7 +4,7 @@ import { useCopyToClipboard } from '../src';
const valueToRaiseMockException = 'fake input causing exception in copy to clipboard';
jest.mock('copy-to-clipboard', () =>
jest.fn().mockImplementation(input => {
jest.fn().mockImplementation((input) => {
if (input === valueToRaiseMockException) {
throw new Error(input);
}
@ -14,15 +14,12 @@ jest.mock('copy-to-clipboard', () =>
describe('useCopyToClipboard', () => {
let hook;
let consoleErrorSpy = jest.spyOn(global.console, 'error').mockImplementation(() => {
});
let consoleErrorSpy = jest.spyOn(global.console, 'error').mockImplementation(() => {});
beforeEach(() => {
hook = renderHook(() => useCopyToClipboard());
});
afterAll(() => {
consoleErrorSpy.mockRestore();
jest.unmock('copy-to-clipboard');

View File

@ -187,15 +187,21 @@ 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 or function returning 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 or function returning 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 or function returning 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();
});

View File

@ -11,7 +11,9 @@ const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup);
it('should run provided object once', () => {
const { rerender: rerenderNormal } = renderHook(() => useEffect(mockEffectNormal, [options]));
const { rerender: rerenderDeep } = renderHook(() => useCustomCompareEffect(mockEffectDeep, [options], isDeepEqual));
const { rerender: rerenderDeep } = renderHook(() =>
useCustomCompareEffect(mockEffectDeep, [options], isDeepEqual)
);
expect(mockEffectNormal).toHaveBeenCalledTimes(1);
expect(mockEffectDeep).toHaveBeenCalledTimes(1);
@ -32,7 +34,9 @@ it('should run provided object once', () => {
});
it('should run clean-up provided on unmount', () => {
const { unmount } = renderHook(() => useCustomCompareEffect(mockEffectCallback, [options], isDeepEqual));
const { unmount } = renderHook(() =>
useCustomCompareEffect(mockEffectCallback, [options], isDeepEqual)
);
expect(mockEffectCleanup).not.toHaveBeenCalled();
unmount();

View File

@ -10,7 +10,9 @@ const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup);
it('should run provided object once', () => {
const { rerender: rerenderNormal } = renderHook(() => useEffect(mockEffectNormal, [options]));
const { rerender: rerenderDeep } = renderHook(() => useDeepCompareEffect(mockEffectDeep, [options]));
const { rerender: rerenderDeep } = renderHook(() =>
useDeepCompareEffect(mockEffectDeep, [options])
);
expect(mockEffectNormal).toHaveBeenCalledTimes(1);
expect(mockEffectDeep).toHaveBeenCalledTimes(1);

View File

@ -1,7 +1,8 @@
import { act, renderHook } from '@testing-library/react-hooks';
import useDefault from '../src/useDefault';
const setUp = (defaultValue: any, initialValue: any) => renderHook(() => useDefault(defaultValue, initialValue));
const setUp = (defaultValue: any, initialValue: any) =>
renderHook(() => useDefault(defaultValue, initialValue));
describe.each`
valueType | defaultValue | initialValue | anotherValue

Some files were not shown because too many files have changed in this diff Show More