(initialValue !== undefined ? initialValue : defaultInitialValue);
16 | return providerFactory({ value: state }, children);
17 | };
18 |
19 | const useStateContext = () => {
20 | const state = useContext(context);
21 | if (state == null) {
22 | throw new Error(`useStateContext must be used inside a StateProvider.`);
23 | }
24 | return state;
25 | };
26 |
27 | return [useStateContext, StateProvider, context] as const;
28 | };
29 |
30 | export default createStateContext;
31 |
--------------------------------------------------------------------------------
/src/misc/hookState.ts:
--------------------------------------------------------------------------------
1 | export type IHookStateInitialSetter = () => S;
2 | export type IHookStateInitAction = S | IHookStateInitialSetter;
3 |
4 | export type IHookStateSetter = ((prevState: S) => S) | (() => S);
5 | export type IHookStateSetAction = S | IHookStateSetter;
6 |
7 | export type IHookStateResolvable = S | IHookStateInitialSetter | IHookStateSetter;
8 |
9 | export function resolveHookState(nextState: IHookStateInitAction): S;
10 | export function resolveHookState(
11 | nextState: IHookStateSetAction,
12 | currentState?: C
13 | ): S;
14 | export function resolveHookState(
15 | nextState: IHookStateResolvable,
16 | currentState?: C
17 | ): S;
18 | export function resolveHookState(
19 | nextState: IHookStateResolvable,
20 | currentState?: C
21 | ): S {
22 | if (typeof nextState === 'function') {
23 | return nextState.length ? (nextState as Function)(currentState) : (nextState as Function)();
24 | }
25 |
26 | return nextState;
27 | }
28 |
--------------------------------------------------------------------------------
/src/misc/isDeepEqual.ts:
--------------------------------------------------------------------------------
1 | import isDeepEqualReact from 'fast-deep-equal/react';
2 |
3 | export default isDeepEqualReact;
4 |
--------------------------------------------------------------------------------
/src/misc/parseTimeRanges.ts:
--------------------------------------------------------------------------------
1 | export default function parseTimeRanges(ranges) {
2 | const result: { start: number; end: number }[] = [];
3 |
4 | for (let i = 0; i < ranges.length; i++) {
5 | result.push({
6 | start: ranges.start(i),
7 | end: ranges.end(i),
8 | });
9 | }
10 |
11 | return result;
12 | }
13 |
--------------------------------------------------------------------------------
/src/misc/types.ts:
--------------------------------------------------------------------------------
1 | export type PromiseType > = P extends Promise ? T : never;
2 |
3 | export type FunctionReturningPromise = (...args: any[]) => Promise;
4 |
--------------------------------------------------------------------------------
/src/misc/util.ts:
--------------------------------------------------------------------------------
1 | export const noop = () => {};
2 |
3 | export function on(
4 | obj: T | null,
5 | ...args: Parameters | [string, Function | null, ...any]
6 | ): void {
7 | if (obj && obj.addEventListener) {
8 | obj.addEventListener(...(args as Parameters));
9 | }
10 | }
11 |
12 | export function off(
13 | obj: T | null,
14 | ...args: Parameters | [string, Function | null, ...any]
15 | ): void {
16 | if (obj && obj.removeEventListener) {
17 | obj.removeEventListener(...(args as Parameters));
18 | }
19 | }
20 |
21 | export const isBrowser = typeof window !== 'undefined';
22 |
23 | export const isNavigator = typeof navigator !== 'undefined';
24 |
--------------------------------------------------------------------------------
/src/useAsync.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, useEffect } from 'react';
2 | import useAsyncFn from './useAsyncFn';
3 | import { FunctionReturningPromise } from './misc/types';
4 |
5 | export { AsyncState, AsyncFnReturn } from './useAsyncFn';
6 |
7 | export default function useAsync(
8 | fn: T,
9 | deps: DependencyList = []
10 | ) {
11 | const [state, callback] = useAsyncFn(fn, deps, {
12 | loading: true,
13 | });
14 |
15 | useEffect(() => {
16 | callback();
17 | }, [callback]);
18 |
19 | return state;
20 | }
21 |
--------------------------------------------------------------------------------
/src/useAsyncRetry.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, useCallback, useState } from 'react';
2 | import useAsync, { AsyncState } from './useAsync';
3 |
4 | export type AsyncStateRetry = AsyncState & {
5 | retry(): void;
6 | };
7 |
8 | const useAsyncRetry = (fn: () => Promise, deps: DependencyList = []) => {
9 | const [attempt, setAttempt] = useState(0);
10 | const state = useAsync(fn, [...deps, attempt]);
11 |
12 | const stateLoading = state.loading;
13 | const retry = useCallback(() => {
14 | if (stateLoading) {
15 | if (process.env.NODE_ENV === 'development') {
16 | console.log(
17 | 'You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.'
18 | );
19 | }
20 |
21 | return;
22 | }
23 |
24 | setAttempt((currentAttempt) => currentAttempt + 1);
25 | }, [...deps, stateLoading]);
26 |
27 | return { ...state, retry };
28 | };
29 |
30 | export default useAsyncRetry;
31 |
--------------------------------------------------------------------------------
/src/useAudio.ts:
--------------------------------------------------------------------------------
1 | import createHTMLMediaHook from './factory/createHTMLMediaHook';
2 |
3 | const useAudio = createHTMLMediaHook('audio');
4 | export default useAudio;
5 |
--------------------------------------------------------------------------------
/src/useBeforeUnload.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from 'react';
2 | import { off, on } from './misc/util';
3 |
4 | const useBeforeUnload = (enabled: boolean | (() => boolean) = true, message?: string) => {
5 | const handler = useCallback(
6 | (event: BeforeUnloadEvent) => {
7 | const finalEnabled = typeof enabled === 'function' ? enabled() : true;
8 |
9 | if (!finalEnabled) {
10 | return;
11 | }
12 |
13 | event.preventDefault();
14 |
15 | if (message) {
16 | event.returnValue = message;
17 | }
18 |
19 | return message;
20 | },
21 | [enabled, message]
22 | );
23 |
24 | useEffect(() => {
25 | if (!enabled) {
26 | return;
27 | }
28 |
29 | on(window, 'beforeunload', handler);
30 |
31 | return () => off(window, 'beforeunload', handler);
32 | }, [enabled, handler]);
33 | };
34 |
35 | export default useBeforeUnload;
36 |
--------------------------------------------------------------------------------
/src/useBoolean.ts:
--------------------------------------------------------------------------------
1 | import useBoolean from './useToggle';
2 |
3 | export default useBoolean;
4 |
--------------------------------------------------------------------------------
/src/useClickAway.ts:
--------------------------------------------------------------------------------
1 | import { RefObject, useEffect, useRef } from 'react';
2 | import { off, on } from './misc/util';
3 |
4 | const defaultEvents = ['mousedown', 'touchstart'];
5 |
6 | const useClickAway = (
7 | ref: RefObject,
8 | onClickAway: (event: E) => void,
9 | events: string[] = defaultEvents
10 | ) => {
11 | const savedCallback = useRef(onClickAway);
12 | useEffect(() => {
13 | savedCallback.current = onClickAway;
14 | }, [onClickAway]);
15 | useEffect(() => {
16 | const handler = (event) => {
17 | const { current: el } = ref;
18 | el && !el.contains(event.target) && savedCallback.current(event);
19 | };
20 | for (const eventName of events) {
21 | on(document, eventName, handler);
22 | }
23 | return () => {
24 | for (const eventName of events) {
25 | off(document, eventName, handler);
26 | }
27 | };
28 | }, [events, ref]);
29 | };
30 |
31 | export default useClickAway;
32 |
--------------------------------------------------------------------------------
/src/useCookie.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 | import Cookies from 'js-cookie';
3 |
4 | const useCookie = (
5 | cookieName: string
6 | ): [string | null, (newValue: string, options?: Cookies.CookieAttributes) => void, () => void] => {
7 | const [value, setValue] = useState(() => Cookies.get(cookieName) || null);
8 |
9 | const updateCookie = useCallback(
10 | (newValue: string, options?: Cookies.CookieAttributes) => {
11 | Cookies.set(cookieName, newValue, options);
12 | setValue(newValue);
13 | },
14 | [cookieName]
15 | );
16 |
17 | const deleteCookie = useCallback(() => {
18 | Cookies.remove(cookieName);
19 | setValue(null);
20 | }, [cookieName]);
21 |
22 | return [value, updateCookie, deleteCookie];
23 | };
24 |
25 | export default useCookie;
26 |
--------------------------------------------------------------------------------
/src/useCss.ts:
--------------------------------------------------------------------------------
1 | import { create, NanoRenderer } from 'nano-css';
2 | import { addon as addonCSSOM, CSSOMAddon } from 'nano-css/addon/cssom';
3 | import { addon as addonVCSSOM, VCSSOMAddon } from 'nano-css/addon/vcssom';
4 | import { cssToTree } from 'nano-css/addon/vcssom/cssToTree';
5 | import { useMemo } from 'react';
6 | import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
7 |
8 | type Nano = NanoRenderer & CSSOMAddon & VCSSOMAddon;
9 | const nano = create() as Nano;
10 | addonCSSOM(nano);
11 | addonVCSSOM(nano);
12 |
13 | let counter = 0;
14 |
15 | const useCss = (css: object): string => {
16 | const className = useMemo(() => 'react-use-css-' + (counter++).toString(36), []);
17 | const sheet = useMemo(() => new nano.VSheet(), []);
18 |
19 | useIsomorphicLayoutEffect(() => {
20 | const tree = {};
21 | cssToTree(tree, css, '.' + className, '');
22 | sheet.diff(tree);
23 |
24 | return () => {
25 | sheet.diff({});
26 | };
27 | });
28 |
29 | return className;
30 | };
31 |
32 | export default useCss;
33 |
--------------------------------------------------------------------------------
/src/useDebounce.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, useEffect } from 'react';
2 | import useTimeoutFn from './useTimeoutFn';
3 |
4 | export type UseDebounceReturn = [() => boolean | null, () => void];
5 |
6 | export default function useDebounce(
7 | fn: Function,
8 | ms: number = 0,
9 | deps: DependencyList = []
10 | ): UseDebounceReturn {
11 | const [isReady, cancel, reset] = useTimeoutFn(fn, ms);
12 |
13 | useEffect(reset, deps);
14 |
15 | return [isReady, cancel];
16 | }
17 |
--------------------------------------------------------------------------------
/src/useDeepCompareEffect.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, EffectCallback } from 'react';
2 | import useCustomCompareEffect from './useCustomCompareEffect';
3 | import isDeepEqual from './misc/isDeepEqual';
4 |
5 | const isPrimitive = (val: any) => val !== Object(val);
6 |
7 | const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
8 | if (process.env.NODE_ENV !== 'production') {
9 | if (!(deps instanceof Array) || !deps.length) {
10 | console.warn(
11 | '`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.'
12 | );
13 | }
14 |
15 | if (deps.every(isPrimitive)) {
16 | console.warn(
17 | '`useDeepCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.'
18 | );
19 | }
20 | }
21 |
22 | useCustomCompareEffect(effect, deps, isDeepEqual);
23 | };
24 |
25 | export default useDeepCompareEffect;
26 |
--------------------------------------------------------------------------------
/src/useDefault.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useDefault = (
4 | defaultValue: TStateType,
5 | initialValue: TStateType | (() => TStateType)
6 | ) => {
7 | const [value, setValue] = useState(initialValue);
8 |
9 | if (value === undefined || value === null) {
10 | return [defaultValue, setValue] as const;
11 | }
12 |
13 | return [value, setValue] as const;
14 | };
15 |
16 | export default useDefault;
17 |
--------------------------------------------------------------------------------
/src/useEffectOnce.ts:
--------------------------------------------------------------------------------
1 | import { EffectCallback, useEffect } from 'react';
2 |
3 | const useEffectOnce = (effect: EffectCallback) => {
4 | useEffect(effect, []);
5 | };
6 |
7 | export default useEffectOnce;
8 |
--------------------------------------------------------------------------------
/src/useEnsuredForwardedRef.ts:
--------------------------------------------------------------------------------
1 | import {
2 | forwardRef,
3 | ForwardRefExoticComponent,
4 | MutableRefObject,
5 | PropsWithChildren,
6 | PropsWithoutRef,
7 | RefAttributes,
8 | RefForwardingComponent,
9 | useEffect,
10 | useRef,
11 | } from 'react';
12 |
13 | export default function useEnsuredForwardedRef(
14 | forwardedRef: MutableRefObject
15 | ): MutableRefObject {
16 | const ensuredRef = useRef(forwardedRef && forwardedRef.current);
17 |
18 | useEffect(() => {
19 | if (!forwardedRef) {
20 | return;
21 | }
22 | forwardedRef.current = ensuredRef.current;
23 | }, [forwardedRef]);
24 |
25 | return ensuredRef;
26 | }
27 |
28 | export function ensuredForwardRef(
29 | Component: RefForwardingComponent
30 | ): ForwardRefExoticComponent & RefAttributes> {
31 | return forwardRef((props: PropsWithChildren, ref) => {
32 | const ensuredRef = useEnsuredForwardedRef(ref as MutableRefObject);
33 | return Component(props, ensuredRef);
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/src/useError.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react';
2 |
3 | const useError = (): ((err: Error) => void) => {
4 | const [error, setError] = useState(null);
5 |
6 | useEffect(() => {
7 | if (error) {
8 | throw error;
9 | }
10 | }, [error]);
11 |
12 | const dispatchError = useCallback((err: Error) => {
13 | setError(err);
14 | }, []);
15 |
16 | return dispatchError;
17 | };
18 |
19 | export default useError;
20 |
--------------------------------------------------------------------------------
/src/useFavicon.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | const useFavicon = (href: string) => {
4 | useEffect(() => {
5 | const link: HTMLLinkElement =
6 | document.querySelector("link[rel*='icon']") || document.createElement('link');
7 | link.type = 'image/x-icon';
8 | link.rel = 'shortcut icon';
9 | link.href = href;
10 | document.getElementsByTagName('head')[0].appendChild(link);
11 | }, [href]);
12 | };
13 |
14 | export default useFavicon;
15 |
--------------------------------------------------------------------------------
/src/useFirstMountState.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | export function useFirstMountState(): boolean {
4 | const isFirst = useRef(true);
5 |
6 | if (isFirst.current) {
7 | isFirst.current = false;
8 |
9 | return true;
10 | }
11 |
12 | return isFirst.current;
13 | }
14 |
--------------------------------------------------------------------------------
/src/useGetSet.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch, useMemo, useRef } from 'react';
2 | import useUpdate from './useUpdate';
3 | import { IHookStateInitAction, IHookStateSetAction, resolveHookState } from './misc/hookState';
4 |
5 | export default function useGetSet(
6 | initialState: IHookStateInitAction
7 | ): [get: () => S, set: Dispatch>] {
8 | const state = useRef(resolveHookState(initialState));
9 | const update = useUpdate();
10 |
11 | return useMemo(
12 | () => [
13 | () => state.current as S,
14 | (newState: IHookStateSetAction) => {
15 | state.current = resolveHookState(newState, state.current);
16 | update();
17 | },
18 | ],
19 | []
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/src/useGetSetState.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef } from 'react';
2 | import useUpdate from './useUpdate';
3 |
4 | const useGetSetState = (
5 | initialState: T = {} as T
6 | ): [() => T, (patch: Partial) => void] => {
7 | if (process.env.NODE_ENV !== 'production') {
8 | if (typeof initialState !== 'object') {
9 | console.error('useGetSetState initial state must be an object.');
10 | }
11 | }
12 |
13 | const update = useUpdate();
14 | const state = useRef({ ...(initialState as object) } as T);
15 | const get = useCallback(() => state.current, []);
16 | const set = useCallback((patch: Partial) => {
17 | if (!patch) {
18 | return;
19 | }
20 | if (process.env.NODE_ENV !== 'production') {
21 | if (typeof patch !== 'object') {
22 | console.error('useGetSetState setter patch must be an object.');
23 | }
24 | }
25 | Object.assign(state.current, patch);
26 | update();
27 | }, []);
28 |
29 | return [get, set];
30 | };
31 |
32 | export default useGetSetState;
33 |
--------------------------------------------------------------------------------
/src/useHarmonicIntervalFn.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | import { clearHarmonicInterval, setHarmonicInterval } from 'set-harmonic-interval';
3 |
4 | const useHarmonicIntervalFn = (fn: Function, delay: number | null = 0) => {
5 | const latestCallback = useRef(() => {});
6 |
7 | useEffect(() => {
8 | latestCallback.current = fn;
9 | });
10 |
11 | useEffect(() => {
12 | if (delay !== null) {
13 | const interval = setHarmonicInterval(() => latestCallback.current(), delay);
14 | return () => clearHarmonicInterval(interval);
15 | }
16 | return undefined;
17 | }, [delay]);
18 | };
19 |
20 | export default useHarmonicIntervalFn;
21 |
--------------------------------------------------------------------------------
/src/useHash.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 | import useLifecycles from './useLifecycles';
3 | import { off, on } from './misc/util';
4 |
5 | /**
6 | * read and write url hash, response to url hash change
7 | */
8 | export const useHash = () => {
9 | const [hash, setHash] = useState(() => window.location.hash);
10 |
11 | const onHashChange = useCallback(() => {
12 | setHash(window.location.hash);
13 | }, []);
14 |
15 | useLifecycles(
16 | () => {
17 | on(window, 'hashchange', onHashChange);
18 | },
19 | () => {
20 | off(window, 'hashchange', onHashChange);
21 | }
22 | );
23 |
24 | const _setHash = useCallback(
25 | (newHash: string) => {
26 | if (newHash !== hash) {
27 | window.location.hash = newHash;
28 | }
29 | },
30 | [hash]
31 | );
32 |
33 | return [hash, _setHash] as const;
34 | };
35 |
--------------------------------------------------------------------------------
/src/useHover.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { noop } from './misc/util';
3 |
4 | const { useState } = React;
5 |
6 | export type Element = ((state: boolean) => React.ReactElement) | React.ReactElement;
7 |
8 | const useHover = (element: Element): [React.ReactElement, boolean] => {
9 | const [state, setState] = useState(false);
10 |
11 | const onMouseEnter = (originalOnMouseEnter?: any) => (event: any) => {
12 | (originalOnMouseEnter || noop)(event);
13 | setState(true);
14 | };
15 | const onMouseLeave = (originalOnMouseLeave?: any) => (event: any) => {
16 | (originalOnMouseLeave || noop)(event);
17 | setState(false);
18 | };
19 |
20 | if (typeof element === 'function') {
21 | element = element(state);
22 | }
23 |
24 | const el = React.cloneElement(element, {
25 | onMouseEnter: onMouseEnter(element.props.onMouseEnter),
26 | onMouseLeave: onMouseLeave(element.props.onMouseLeave),
27 | });
28 |
29 | return [el, state];
30 | };
31 |
32 | export default useHover;
33 |
--------------------------------------------------------------------------------
/src/useIntersection.ts:
--------------------------------------------------------------------------------
1 | import { RefObject, useEffect, useState } from 'react';
2 |
3 | const useIntersection = (
4 | ref: RefObject,
5 | options: IntersectionObserverInit
6 | ): IntersectionObserverEntry | null => {
7 | const [intersectionObserverEntry, setIntersectionObserverEntry] =
8 | useState(null);
9 |
10 | useEffect(() => {
11 | if (ref.current && typeof IntersectionObserver === 'function') {
12 | const handler = (entries: IntersectionObserverEntry[]) => {
13 | setIntersectionObserverEntry(entries[0]);
14 | };
15 |
16 | const observer = new IntersectionObserver(handler, options);
17 | observer.observe(ref.current);
18 |
19 | return () => {
20 | setIntersectionObserverEntry(null);
21 | observer.disconnect();
22 | };
23 | }
24 | return () => {};
25 | }, [ref.current, options.threshold, options.root, options.rootMargin]);
26 |
27 | return intersectionObserverEntry;
28 | };
29 |
30 | export default useIntersection;
31 |
--------------------------------------------------------------------------------
/src/useInterval.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | const useInterval = (callback: Function, delay?: number | null) => {
4 | const savedCallback = useRef(() => {});
5 |
6 | useEffect(() => {
7 | savedCallback.current = callback;
8 | });
9 |
10 | useEffect(() => {
11 | if (delay !== null) {
12 | const interval = setInterval(() => savedCallback.current(), delay || 0);
13 | return () => clearInterval(interval);
14 | }
15 |
16 | return undefined;
17 | }, [delay]);
18 | };
19 |
20 | export default useInterval;
21 |
--------------------------------------------------------------------------------
/src/useIsomorphicLayoutEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useLayoutEffect } from 'react';
2 | import { isBrowser } from './misc/util';
3 |
4 | const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
5 |
6 | export default useIsomorphicLayoutEffect;
7 |
--------------------------------------------------------------------------------
/src/useKeyPress.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import useKey, { KeyFilter } from './useKey';
3 |
4 | const useKeyPress = (keyFilter: KeyFilter) => {
5 | const [state, set] = useState<[boolean, null | KeyboardEvent]>([false, null]);
6 | useKey(keyFilter, (event) => set([true, event]), { event: 'keydown' }, [state]);
7 | useKey(keyFilter, (event) => set([false, event]), { event: 'keyup' }, [state]);
8 | return state;
9 | };
10 |
11 | export default useKeyPress;
12 |
--------------------------------------------------------------------------------
/src/useKeyPressEvent.ts:
--------------------------------------------------------------------------------
1 | import { Handler, KeyFilter } from './useKey';
2 | import useKeyPressDefault from './useKeyPress';
3 | import useUpdateEffect from './useUpdateEffect';
4 |
5 | const useKeyPressEvent = (
6 | key: string | KeyFilter,
7 | keydown?: Handler | null | undefined,
8 | keyup?: Handler | null | undefined,
9 | useKeyPress = useKeyPressDefault
10 | ) => {
11 | const [pressed, event] = useKeyPress(key);
12 | useUpdateEffect(() => {
13 | if (!pressed && keyup) {
14 | keyup(event!);
15 | } else if (pressed && keydown) {
16 | keydown(event!);
17 | }
18 | }, [pressed]);
19 | };
20 |
21 | export default useKeyPressEvent;
22 |
--------------------------------------------------------------------------------
/src/useKeyboardJs.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import useMount from './useMount';
3 |
4 | const useKeyboardJs = (combination: string | string[]) => {
5 | const [state, set] = useState<[boolean, null | KeyboardEvent]>([false, null]);
6 | const [keyboardJs, setKeyboardJs] = useState(null);
7 |
8 | useMount(() => {
9 | import('keyboardjs').then((k) => setKeyboardJs(k.default || k));
10 | });
11 |
12 | useEffect(() => {
13 | if (!keyboardJs) {
14 | return;
15 | }
16 |
17 | const down = (event) => set([true, event]);
18 | const up = (event) => set([false, event]);
19 | keyboardJs.bind(combination, down, up, true);
20 |
21 | return () => {
22 | keyboardJs.unbind(combination, down, up);
23 | };
24 | }, [combination, keyboardJs]);
25 |
26 | return state;
27 | };
28 |
29 | export default useKeyboardJs;
30 |
--------------------------------------------------------------------------------
/src/useLatest.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | const useLatest = (value: T): { readonly current: T } => {
4 | const ref = useRef(value);
5 | ref.current = value;
6 | return ref;
7 | };
8 |
9 | export default useLatest;
10 |
--------------------------------------------------------------------------------
/src/useLifecycles.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | const useLifecycles = (mount, unmount?) => {
4 | useEffect(() => {
5 | if (mount) {
6 | mount();
7 | }
8 | return () => {
9 | if (unmount) {
10 | unmount();
11 | }
12 | };
13 | }, []);
14 | };
15 |
16 | export default useLifecycles;
17 |
--------------------------------------------------------------------------------
/src/useLogger.ts:
--------------------------------------------------------------------------------
1 | import useEffectOnce from './useEffectOnce';
2 | import useUpdateEffect from './useUpdateEffect';
3 |
4 | const useLogger = (componentName: string, ...rest) => {
5 | useEffectOnce(() => {
6 | console.log(`${componentName} mounted`, ...rest);
7 | return () => console.log(`${componentName} unmounted`);
8 | });
9 |
10 | useUpdateEffect(() => {
11 | console.log(`${componentName} updated`, ...rest);
12 | });
13 | };
14 |
15 | export default useLogger;
16 |
--------------------------------------------------------------------------------
/src/useMediaDevices.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { isNavigator, noop, off, on } from './misc/util';
3 |
4 | const useMediaDevices = () => {
5 | const [state, setState] = useState({});
6 |
7 | useEffect(() => {
8 | let mounted = true;
9 |
10 | const onChange = () => {
11 | navigator.mediaDevices
12 | .enumerateDevices()
13 | .then((devices) => {
14 | if (mounted) {
15 | setState({
16 | devices: devices.map(({ deviceId, groupId, kind, label }) => ({
17 | deviceId,
18 | groupId,
19 | kind,
20 | label,
21 | })),
22 | });
23 | }
24 | })
25 | .catch(noop);
26 | };
27 |
28 | on(navigator.mediaDevices, 'devicechange', onChange);
29 | onChange();
30 |
31 | return () => {
32 | mounted = false;
33 | off(navigator.mediaDevices, 'devicechange', onChange);
34 | };
35 | }, []);
36 |
37 | return state;
38 | };
39 |
40 | const useMediaDevicesMock = () => ({});
41 |
42 | export default isNavigator && !!navigator.mediaDevices ? useMediaDevices : useMediaDevicesMock;
43 |
--------------------------------------------------------------------------------
/src/useMount.ts:
--------------------------------------------------------------------------------
1 | import useEffectOnce from './useEffectOnce';
2 |
3 | const useMount = (fn: () => void) => {
4 | useEffectOnce(() => {
5 | fn();
6 | });
7 | };
8 |
9 | export default useMount;
10 |
--------------------------------------------------------------------------------
/src/useMountedState.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from 'react';
2 |
3 | export default function useMountedState(): () => boolean {
4 | const mountedRef = useRef(false);
5 | const get = useCallback(() => mountedRef.current, []);
6 |
7 | useEffect(() => {
8 | mountedRef.current = true;
9 |
10 | return () => {
11 | mountedRef.current = false;
12 | };
13 | }, []);
14 |
15 | return get;
16 | }
17 |
--------------------------------------------------------------------------------
/src/useMouseHovered.ts:
--------------------------------------------------------------------------------
1 | import { RefObject } from 'react';
2 | import useHoverDirty from './useHoverDirty';
3 | import useMouse, { State } from './useMouse';
4 |
5 | export interface UseMouseHoveredOptions {
6 | whenHovered?: boolean;
7 | bound?: boolean;
8 | }
9 |
10 | const nullRef = { current: null };
11 |
12 | const useMouseHovered = (ref: RefObject, options: UseMouseHoveredOptions = {}): State => {
13 | const whenHovered = !!options.whenHovered;
14 | const bound = !!options.bound;
15 |
16 | const isHovered = useHoverDirty(ref, whenHovered);
17 | const state = useMouse(whenHovered && !isHovered ? nullRef : ref);
18 |
19 | if (bound) {
20 | state.elX = Math.max(0, Math.min(state.elX, state.elW));
21 | state.elY = Math.max(0, Math.min(state.elY, state.elH));
22 | }
23 |
24 | return state;
25 | };
26 |
27 | export default useMouseHovered;
28 |
--------------------------------------------------------------------------------
/src/useMouseWheel.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { off, on } from './misc/util';
3 |
4 | export default () => {
5 | const [mouseWheelScrolled, setMouseWheelScrolled] = useState(0);
6 | useEffect(() => {
7 | const updateScroll = (e: MouseWheelEvent) => {
8 | setMouseWheelScrolled(e.deltaY + mouseWheelScrolled);
9 | };
10 | on(window, 'wheel', updateScroll, false);
11 | return () => off(window, 'wheel', updateScroll);
12 | });
13 | return mouseWheelScrolled;
14 | };
15 |
--------------------------------------------------------------------------------
/src/useNumber.ts:
--------------------------------------------------------------------------------
1 | import useNumber from './useCounter';
2 |
3 | export default useNumber;
4 |
--------------------------------------------------------------------------------
/src/useObservable.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
3 |
4 | export interface Observable {
5 | subscribe: (listener: (value: T) => void) => {
6 | unsubscribe: () => void;
7 | };
8 | }
9 |
10 | function useObservable(observable$: Observable): T | undefined;
11 | function useObservable(observable$: Observable, initialValue: T): T;
12 | function useObservable(observable$: Observable, initialValue?: T): T | undefined {
13 | const [value, update] = useState(initialValue);
14 |
15 | useIsomorphicLayoutEffect(() => {
16 | const s = observable$.subscribe(update);
17 | return () => s.unsubscribe();
18 | }, [observable$]);
19 |
20 | return value;
21 | }
22 |
23 | export default useObservable;
24 |
--------------------------------------------------------------------------------
/src/usePageLeave.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { off, on } from './misc/util';
3 |
4 | const usePageLeave = (onPageLeave, args = []) => {
5 | useEffect(() => {
6 | if (!onPageLeave) {
7 | return;
8 | }
9 |
10 | const handler = (event) => {
11 | event = event ? event : (window.event as any);
12 | const from = event.relatedTarget || event.toElement;
13 | if (!from || (from as any).nodeName === 'HTML') {
14 | onPageLeave();
15 | }
16 | };
17 |
18 | on(document, 'mouseout', handler);
19 | return () => {
20 | off(document, 'mouseout', handler);
21 | };
22 | }, args);
23 | };
24 |
25 | export default usePageLeave;
26 |
--------------------------------------------------------------------------------
/src/usePrevious.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | export default function usePrevious(state: T): T | undefined {
4 | const ref = useRef();
5 |
6 | useEffect(() => {
7 | ref.current = state;
8 | });
9 |
10 | return ref.current;
11 | }
12 |
--------------------------------------------------------------------------------
/src/usePreviousDistinct.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import { useFirstMountState } from './useFirstMountState';
3 |
4 | export type Predicate = (prev: T | undefined, next: T) => boolean;
5 |
6 | const strictEquals = (prev: T | undefined, next: T) => prev === next;
7 |
8 | export default function usePreviousDistinct(
9 | value: T,
10 | compare: Predicate = strictEquals
11 | ): T | undefined {
12 | const prevRef = useRef();
13 | const curRef = useRef(value);
14 | const isFirstMount = useFirstMountState();
15 |
16 | if (!isFirstMount && !compare(curRef.current, value)) {
17 | prevRef.current = curRef.current;
18 | curRef.current = value;
19 | }
20 |
21 | return prevRef.current;
22 | }
23 |
--------------------------------------------------------------------------------
/src/usePromise.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import useMountedState from './useMountedState';
3 |
4 | export type UsePromise = () => (promise: Promise) => Promise;
5 |
6 | const usePromise: UsePromise = () => {
7 | const isMounted = useMountedState();
8 | return useCallback(
9 | (promise: Promise) =>
10 | new Promise((resolve, reject) => {
11 | const onValue = (value) => {
12 | isMounted() && resolve(value);
13 | };
14 | const onError = (error) => {
15 | isMounted() && reject(error);
16 | };
17 | promise.then(onValue, onError);
18 | }),
19 | []
20 | );
21 | };
22 |
23 | export default usePromise;
24 |
--------------------------------------------------------------------------------
/src/useQueue.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export interface QueueMethods {
4 | add: (item: T) => void;
5 | remove: () => T;
6 | first: T;
7 | last: T;
8 | size: number;
9 | }
10 |
11 | const useQueue = (initialValue: T[] = []): QueueMethods => {
12 | const [state, set] = useState(initialValue);
13 | return {
14 | add: (value) => {
15 | set((queue) => [...queue, value]);
16 | },
17 | remove: () => {
18 | let result;
19 | set(([first, ...rest]) => {
20 | result = first;
21 | return rest;
22 | });
23 | return result;
24 | },
25 | get first() {
26 | return state[0];
27 | },
28 | get last() {
29 | return state[state.length - 1];
30 | },
31 | get size() {
32 | return state.length;
33 | },
34 | };
35 | };
36 |
37 | export default useQueue;
38 |
--------------------------------------------------------------------------------
/src/useRaf.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
3 |
4 | const useRaf = (ms: number = 1e12, delay: number = 0): number => {
5 | const [elapsed, set] = useState(0);
6 |
7 | useIsomorphicLayoutEffect(() => {
8 | let raf;
9 | let timerStop;
10 | let start;
11 |
12 | const onFrame = () => {
13 | const time = Math.min(1, (Date.now() - start) / ms);
14 | set(time);
15 | loop();
16 | };
17 | const loop = () => {
18 | raf = requestAnimationFrame(onFrame);
19 | };
20 | const onStart = () => {
21 | timerStop = setTimeout(() => {
22 | cancelAnimationFrame(raf);
23 | set(1);
24 | }, ms);
25 | start = Date.now();
26 | loop();
27 | };
28 | const timerDelay = setTimeout(onStart, delay);
29 |
30 | return () => {
31 | clearTimeout(timerStop);
32 | clearTimeout(timerDelay);
33 | cancelAnimationFrame(raf);
34 | };
35 | }, [ms, delay]);
36 |
37 | return elapsed;
38 | };
39 |
40 | export default useRaf;
41 |
--------------------------------------------------------------------------------
/src/useRafState.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction, useCallback, useRef, useState } from 'react';
2 |
3 | import useUnmount from './useUnmount';
4 |
5 | const useRafState = (initialState: S | (() => S)): [S, Dispatch>] => {
6 | const frame = useRef(0);
7 | const [state, setState] = useState(initialState);
8 |
9 | const setRafState = useCallback((value: S | ((prevState: S) => S)) => {
10 | cancelAnimationFrame(frame.current);
11 |
12 | frame.current = requestAnimationFrame(() => {
13 | setState(value);
14 | });
15 | }, []);
16 |
17 | useUnmount(() => {
18 | cancelAnimationFrame(frame.current);
19 | });
20 |
21 | return [state, setRafState];
22 | };
23 |
24 | export default useRafState;
25 |
--------------------------------------------------------------------------------
/src/useRendersCount.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | export function useRendersCount(): number {
4 | return ++useRef(0).current;
5 | }
6 |
--------------------------------------------------------------------------------
/src/useScrollbarWidth.ts:
--------------------------------------------------------------------------------
1 | import { scrollbarWidth } from '@xobotyi/scrollbar-width';
2 | import { useEffect, useState } from 'react';
3 |
4 | export function useScrollbarWidth(): number | undefined {
5 | const [sbw, setSbw] = useState(scrollbarWidth());
6 |
7 | // this needed to ensure the scrollbar width in case hook called before the DOM is ready
8 | useEffect(() => {
9 | if (typeof sbw !== 'undefined') {
10 | return;
11 | }
12 |
13 | const raf = requestAnimationFrame(() => {
14 | setSbw(scrollbarWidth());
15 | });
16 |
17 | return () => cancelAnimationFrame(raf);
18 | }, []);
19 |
20 | return sbw;
21 | }
22 |
--------------------------------------------------------------------------------
/src/useScrolling.ts:
--------------------------------------------------------------------------------
1 | import { RefObject, useEffect, useState } from 'react';
2 | import { off, on } from './misc/util';
3 |
4 | const useScrolling = (ref: RefObject): boolean => {
5 | const [scrolling, setScrolling] = useState(false);
6 |
7 | useEffect(() => {
8 | if (ref.current) {
9 | let scrollingTimeout;
10 |
11 | const handleScrollEnd = () => {
12 | setScrolling(false);
13 | };
14 |
15 | const handleScroll = () => {
16 | setScrolling(true);
17 | clearTimeout(scrollingTimeout);
18 | scrollingTimeout = setTimeout(() => handleScrollEnd(), 150);
19 | };
20 |
21 | on(ref.current, 'scroll', handleScroll, false);
22 | return () => {
23 | if (ref.current) {
24 | off(ref.current, 'scroll', handleScroll, false);
25 | }
26 | };
27 | }
28 | return () => {};
29 | }, [ref]);
30 |
31 | return scrolling;
32 | };
33 |
34 | export default useScrolling;
35 |
--------------------------------------------------------------------------------
/src/useSearchParam.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { isBrowser, off, on } from './misc/util';
3 |
4 | const getValue = (search: string, param: string) => new URLSearchParams(search).get(param);
5 |
6 | export type UseQueryParam = (param: string) => string | null;
7 |
8 | const useSearchParam: UseQueryParam = (param) => {
9 | const location = window.location;
10 | const [value, setValue] = useState(() => getValue(location.search, param));
11 |
12 | useEffect(() => {
13 | const onChange = () => {
14 | setValue(getValue(location.search, param));
15 | };
16 |
17 | on(window, 'popstate', onChange);
18 | on(window, 'pushstate', onChange);
19 | on(window, 'replacestate', onChange);
20 |
21 | return () => {
22 | off(window, 'popstate', onChange);
23 | off(window, 'pushstate', onChange);
24 | off(window, 'replacestate', onChange);
25 | };
26 | }, []);
27 |
28 | return value;
29 | };
30 |
31 | const useSearchParamServer = () => null;
32 |
33 | export default isBrowser ? useSearchParam : useSearchParamServer;
34 |
--------------------------------------------------------------------------------
/src/useSetState.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 |
3 | const useSetState = (
4 | initialState: T = {} as T
5 | ): [T, (patch: Partial | ((prevState: T) => Partial)) => void] => {
6 | const [state, set] = useState(initialState);
7 | const setState = useCallback((patch) => {
8 | set((prevState) =>
9 | Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch)
10 | );
11 | }, []);
12 |
13 | return [state, setState];
14 | };
15 |
16 | export default useSetState;
17 |
--------------------------------------------------------------------------------
/src/useShallowCompareEffect.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, EffectCallback } from 'react';
2 | import { equal as isShallowEqual } from 'fast-shallow-equal';
3 | import useCustomCompareEffect from './useCustomCompareEffect';
4 |
5 | const isPrimitive = (val: any) => val !== Object(val);
6 | const shallowEqualDepsList = (prevDeps: DependencyList, nextDeps: DependencyList) =>
7 | prevDeps.every((dep, index) => isShallowEqual(dep, nextDeps[index]));
8 |
9 | const useShallowCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
10 | if (process.env.NODE_ENV !== 'production') {
11 | if (!(deps instanceof Array) || !deps.length) {
12 | console.warn(
13 | '`useShallowCompareEffect` should not be used with no dependencies. Use React.useEffect instead.'
14 | );
15 | }
16 |
17 | if (deps.every(isPrimitive)) {
18 | console.warn(
19 | '`useShallowCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.'
20 | );
21 | }
22 | }
23 |
24 | useCustomCompareEffect(effect, deps, shallowEqualDepsList);
25 | };
26 |
27 | export default useShallowCompareEffect;
28 |
--------------------------------------------------------------------------------
/src/useThrottle.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 | import useUnmount from './useUnmount';
3 |
4 | const useThrottle = (value: T, ms: number = 200) => {
5 | const [state, setState] = useState(value);
6 | const timeout = useRef>();
7 | const nextValue = useRef(null) as any;
8 | const hasNextValue = useRef(0) as any;
9 |
10 | useEffect(() => {
11 | if (!timeout.current) {
12 | setState(value);
13 | const timeoutCallback = () => {
14 | if (hasNextValue.current) {
15 | hasNextValue.current = false;
16 | setState(nextValue.current);
17 | timeout.current = setTimeout(timeoutCallback, ms);
18 | } else {
19 | timeout.current = undefined;
20 | }
21 | };
22 | timeout.current = setTimeout(timeoutCallback, ms);
23 | } else {
24 | nextValue.current = value;
25 | hasNextValue.current = true;
26 | }
27 | }, [value]);
28 |
29 | useUnmount(() => {
30 | timeout.current && clearTimeout(timeout.current);
31 | });
32 |
33 | return state;
34 | };
35 |
36 | export default useThrottle;
37 |
--------------------------------------------------------------------------------
/src/useThrottleFn.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 | import useUnmount from './useUnmount';
3 |
4 | const useThrottleFn = (fn: (...args: U) => T, ms: number = 200, args: U) => {
5 | const [state, setState] = useState(null);
6 | const timeout = useRef>();
7 | const nextArgs = useRef();
8 |
9 | useEffect(() => {
10 | if (!timeout.current) {
11 | setState(fn(...args));
12 | const timeoutCallback = () => {
13 | if (nextArgs.current) {
14 | setState(fn(...nextArgs.current));
15 | nextArgs.current = undefined;
16 | timeout.current = setTimeout(timeoutCallback, ms);
17 | } else {
18 | timeout.current = undefined;
19 | }
20 | };
21 | timeout.current = setTimeout(timeoutCallback, ms);
22 | } else {
23 | nextArgs.current = args;
24 | }
25 | }, args);
26 |
27 | useUnmount(() => {
28 | timeout.current && clearTimeout(timeout.current);
29 | });
30 |
31 | return state;
32 | };
33 |
34 | export default useThrottleFn;
35 |
--------------------------------------------------------------------------------
/src/useTimeout.ts:
--------------------------------------------------------------------------------
1 | import useTimeoutFn from './useTimeoutFn';
2 | import useUpdate from './useUpdate';
3 |
4 | export type UseTimeoutReturn = [() => boolean | null, () => void, () => void];
5 |
6 | export default function useTimeout(ms: number = 0): UseTimeoutReturn {
7 | const update = useUpdate();
8 |
9 | return useTimeoutFn(update, ms);
10 | }
11 |
--------------------------------------------------------------------------------
/src/useTitle.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | export interface UseTitleOptions {
4 | restoreOnUnmount?: boolean;
5 | }
6 |
7 | const DEFAULT_USE_TITLE_OPTIONS: UseTitleOptions = {
8 | restoreOnUnmount: false,
9 | };
10 |
11 | function useTitle(title: string, options: UseTitleOptions = DEFAULT_USE_TITLE_OPTIONS) {
12 | const prevTitleRef = useRef(document.title);
13 |
14 | if (document.title !== title) document.title = title;
15 |
16 | useEffect(() => {
17 | if (options && options.restoreOnUnmount) {
18 | return () => {
19 | document.title = prevTitleRef.current;
20 | };
21 | } else {
22 | return;
23 | }
24 | }, []);
25 | }
26 |
27 | export default typeof document !== 'undefined' ? useTitle : (_title: string) => {};
28 |
--------------------------------------------------------------------------------
/src/useToggle.ts:
--------------------------------------------------------------------------------
1 | import { Reducer, useReducer } from 'react';
2 |
3 | const toggleReducer = (state: boolean, nextValue?: any) =>
4 | typeof nextValue === 'boolean' ? nextValue : !state;
5 |
6 | const useToggle = (initialValue: boolean): [boolean, (nextValue?: any) => void] => {
7 | return useReducer>(toggleReducer, initialValue);
8 | };
9 |
10 | export default useToggle;
11 |
--------------------------------------------------------------------------------
/src/useTween.ts:
--------------------------------------------------------------------------------
1 | import { easing } from 'ts-easing';
2 | import useRaf from './useRaf';
3 |
4 | export type Easing = (t: number) => number;
5 |
6 | const useTween = (easingName: string = 'inCirc', ms: number = 200, delay: number = 0): number => {
7 | const fn: Easing = easing[easingName];
8 | const t = useRaf(ms, delay);
9 |
10 | if (process.env.NODE_ENV !== 'production') {
11 | if (typeof fn !== 'function') {
12 | console.error(
13 | 'useTween() expected "easingName" property to be a valid easing function name, like:' +
14 | '"' +
15 | Object.keys(easing).join('", "') +
16 | '".'
17 | );
18 | console.trace();
19 | return 0;
20 | }
21 | }
22 |
23 | return fn(t);
24 | };
25 |
26 | export default useTween;
27 |
--------------------------------------------------------------------------------
/src/useUnmount.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import useEffectOnce from './useEffectOnce';
3 |
4 | const useUnmount = (fn: () => any): void => {
5 | const fnRef = useRef(fn);
6 |
7 | // update the ref each render so if it change the newest callback will be invoked
8 | fnRef.current = fn;
9 |
10 | useEffectOnce(() => () => fnRef.current());
11 | };
12 |
13 | export default useUnmount;
14 |
--------------------------------------------------------------------------------
/src/useUnmountPromise.ts:
--------------------------------------------------------------------------------
1 | import { useMemo, useRef } from 'react';
2 | import useEffectOnce from './useEffectOnce';
3 |
4 | export type Race = , E = any>(promise: P, onError?: (error: E) => void) => P;
5 |
6 | const useUnmountPromise = (): Race => {
7 | const refUnmounted = useRef(false);
8 | useEffectOnce(() => () => {
9 | refUnmounted.current = true;
10 | });
11 |
12 | const wrapper = useMemo(() => {
13 | const race =
, E>(promise: P, onError?: (error: E) => void) => {
14 | const newPromise: P = new Promise((resolve, reject) => {
15 | promise.then(
16 | (result) => {
17 | if (!refUnmounted.current) resolve(result);
18 | },
19 | (error) => {
20 | if (!refUnmounted.current) reject(error);
21 | else if (onError) onError(error);
22 | else console.error('useUnmountPromise', error);
23 | }
24 | );
25 | }) as P;
26 | return newPromise;
27 | };
28 | return race;
29 | }, []);
30 |
31 | return wrapper;
32 | };
33 |
34 | export default useUnmountPromise;
35 |
--------------------------------------------------------------------------------
/src/useUpdate.ts:
--------------------------------------------------------------------------------
1 | import { useReducer } from 'react';
2 |
3 | const updateReducer = (num: number): number => (num + 1) % 1_000_000;
4 |
5 | export default function useUpdate(): () => void {
6 | const [, update] = useReducer(updateReducer, 0);
7 |
8 | return update;
9 | }
10 |
--------------------------------------------------------------------------------
/src/useUpdateEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useFirstMountState } from './useFirstMountState';
3 |
4 | const useUpdateEffect: typeof useEffect = (effect, deps) => {
5 | const isFirstMount = useFirstMountState();
6 |
7 | useEffect(() => {
8 | if (!isFirstMount) {
9 | return effect();
10 | }
11 | }, deps);
12 | };
13 |
14 | export default useUpdateEffect;
15 |
--------------------------------------------------------------------------------
/src/useUpsert.ts:
--------------------------------------------------------------------------------
1 | import useList, { ListActions } from './useList';
2 | import { IHookStateInitAction } from './misc/hookState';
3 |
4 | export interface UpsertListActions extends Omit, 'upsert'> {
5 | upsert: (newItem: T) => void;
6 | }
7 |
8 | /**
9 | * @deprecated Use `useList` hook's upsert action instead
10 | */
11 | export default function useUpsert(
12 | predicate: (a: T, b: T) => boolean,
13 | initialList: IHookStateInitAction = []
14 | ): [T[], UpsertListActions] {
15 | const [list, listActions] = useList(initialList);
16 |
17 | return [
18 | list,
19 | {
20 | ...listActions,
21 | upsert: (newItem: T) => {
22 | listActions.upsert(predicate, newItem);
23 | },
24 | } as UpsertListActions,
25 | ];
26 | }
27 |
--------------------------------------------------------------------------------
/src/useVibrate.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { isNavigator, noop } from './misc/util';
3 |
4 | export type VibrationPattern = number | number[];
5 |
6 | const isVibrationApiSupported = isNavigator && 'vibrate' in navigator;
7 |
8 | function useVibrate(
9 | enabled: boolean = true,
10 | pattern: VibrationPattern = [1000, 1000],
11 | loop: boolean = true
12 | ): void {
13 | useEffect(() => {
14 | let interval;
15 |
16 | if (enabled) {
17 | navigator.vibrate(pattern);
18 |
19 | if (loop) {
20 | const duration =
21 | pattern instanceof Array ? pattern.reduce((a, b) => a + b) : (pattern as number);
22 |
23 | interval = setInterval(() => {
24 | navigator.vibrate(pattern);
25 | }, duration);
26 | }
27 | }
28 |
29 | return () => {
30 | if (enabled) {
31 | navigator.vibrate(0);
32 |
33 | if (loop) {
34 | clearInterval(interval);
35 | }
36 | }
37 | };
38 | }, [enabled]);
39 | }
40 |
41 | export default isVibrationApiSupported ? useVibrate : noop;
42 |
--------------------------------------------------------------------------------
/src/useVideo.ts:
--------------------------------------------------------------------------------
1 | import createHTMLMediaHook from './factory/createHTMLMediaHook';
2 |
3 | const useVideo = createHTMLMediaHook('video');
4 |
5 | export default useVideo;
6 |
--------------------------------------------------------------------------------
/src/useWait.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamich/react-use/ad33f76dfff7ddb041a9ef74b80656a94affaa80/src/useWait.ts
--------------------------------------------------------------------------------
/stories/comps/UseKey.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import UseKey from '../../src/component/UseKey';
4 |
5 | storiesOf('Components/', module).add('Demo', () => (
6 |
7 | Press "q" key!
8 | alert('Q pressed!')} />
9 |
10 | ));
11 |
--------------------------------------------------------------------------------
/stories/createBreakpoint.story.tsx:
--------------------------------------------------------------------------------
1 | import { withKnobs } from '@storybook/addon-knobs';
2 | import { storiesOf } from '@storybook/react';
3 | import React from 'react';
4 | import { createBreakpoint } from '../src';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const useBreakpointA = createBreakpoint();
8 | const useBreakpointB = createBreakpoint({ mobileM: 350, laptop: 1024, tablet: 768 });
9 |
10 | const Demo = () => {
11 | const breakpointA = useBreakpointA();
12 | const breakpointB = useBreakpointB();
13 | return (
14 |
15 |
{'try resize your window'}
16 |
{'createBreakpoint() #default : { laptopL: 1440, laptop: 1024, tablet: 768 }'}
17 |
{breakpointA}
18 |
{'createBreakpoint({ mobileM: 350, laptop: 1024, tablet: 768 })'}
19 |
{breakpointB}
20 |
21 | );
22 | };
23 |
24 | storiesOf('sensors/createBreakpoint', module)
25 | .addDecorator(withKnobs)
26 | .add('Docs', () => )
27 | .add('Demo', () => {
28 | return ;
29 | });
30 |
--------------------------------------------------------------------------------
/stories/createGlobalState.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React, { FC } from 'react';
3 | import { createGlobalState } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const useGlobalValue = createGlobalState(0);
7 |
8 | const CompA: FC = () => {
9 | const [value, setValue] = useGlobalValue();
10 |
11 | return setValue(value + 1)}>+ ;
12 | };
13 |
14 | const CompB: FC = () => {
15 | const [value, setValue] = useGlobalValue();
16 |
17 | return setValue(value - 1)}>- ;
18 | };
19 |
20 | const Demo: FC = () => {
21 | const [value] = useGlobalValue();
22 | return (
23 |
24 |
{value}
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | storiesOf('State/createGlobalState', module)
32 | .add('Docs', () => )
33 | .add('Demo', () => );
34 |
--------------------------------------------------------------------------------
/stories/createMemo.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { createMemo } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const fibonacci = (n) => {
7 | if (n === 0) {
8 | return 0;
9 | }
10 | if (n === 1) {
11 | return 1;
12 | }
13 | return fibonacci(n - 1) + fibonacci(n - 2);
14 | };
15 |
16 | const useMemoFibonacci = createMemo(fibonacci);
17 |
18 | const Demo = () => {
19 | const result = useMemoFibonacci(10);
20 |
21 | return fib(10) = {result}
;
22 | };
23 |
24 | storiesOf('State/createMemo', module)
25 | .add('Docs', () => )
26 | .add('Demo', () => );
27 |
--------------------------------------------------------------------------------
/stories/useAsyncFn.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useAsyncFn } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [state, callback] = useAsyncFn(
8 | () =>
9 | new Promise((resolve, reject) => {
10 | setTimeout(() => {
11 | if (Math.random() > 0.5) {
12 | resolve('✌️');
13 | } else {
14 | reject(new Error('A pseudo random error occurred'));
15 | }
16 | }, 1000);
17 | })
18 | );
19 |
20 | return (
21 |
22 | {state.loading ? (
23 |
Loading...
24 | ) : state.error ? (
25 |
Error: {state.error.message}
26 | ) : (
27 |
Value: {state.value}
28 | )}
29 |
callback()}>Start
30 |
{JSON.stringify(state, null, 2)}
31 |
32 | );
33 | };
34 |
35 | storiesOf('Side effects/useAsyncFn', module)
36 | .add('Docs', () => )
37 | .add('Demo', () => );
38 |
--------------------------------------------------------------------------------
/stories/useBoolean.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useBoolean } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [on, toggle] = useBoolean(true);
8 |
9 | return (
10 |
11 |
{on ? 'ON' : 'OFF'}
12 |
toggle()}>Toggle
13 |
toggle(true)}>set ON
14 |
toggle(false)}>set OFF
15 |
16 | );
17 | };
18 |
19 | storiesOf('State/useBoolean', module)
20 | .add('Docs', () => )
21 | .add('Demo', () => );
22 |
--------------------------------------------------------------------------------
/stories/useClickAway.story.tsx:
--------------------------------------------------------------------------------
1 | import { action } from '@storybook/addon-actions';
2 | import { storiesOf } from '@storybook/react';
3 | import * as React from 'react';
4 | import { useRef } from 'react';
5 | import { useClickAway } from '../src';
6 | import ShowDocs from './util/ShowDocs';
7 |
8 | const Demo = () => {
9 | const ref = useRef(null);
10 | useClickAway(ref, action('outside clicked'));
11 |
12 | return (
13 |
21 | );
22 | };
23 |
24 | storiesOf('UI/useClickAway', module)
25 | .add('Docs', () => )
26 | .add('Demo', () => );
27 |
--------------------------------------------------------------------------------
/stories/useCookie.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React, { useState, useEffect } from 'react';
3 | import { useCookie } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [value, updateCookie, deleteCookie] = useCookie('my-cookie');
8 | const [counter, setCounter] = useState(1);
9 |
10 | useEffect(() => {
11 | deleteCookie();
12 | }, []);
13 |
14 | const updateCookieHandler = () => {
15 | updateCookie(`my-awesome-cookie-${counter}`);
16 | setCounter((c) => c + 1);
17 | };
18 |
19 | return (
20 |
21 |
Value: {value}
22 |
Update Cookie
23 |
24 |
Delete Cookie
25 |
26 | );
27 | };
28 |
29 | storiesOf('Side effects/useCookie', module)
30 | .add('Docs', () => )
31 | .add('Demo', () => );
32 |
--------------------------------------------------------------------------------
/stories/useCss.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useCss } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const className = useCss({
8 | color: 'red',
9 | border: '1px solid red',
10 | '&:hover': {
11 | color: 'blue',
12 | },
13 | });
14 |
15 | return hello
;
16 | };
17 |
18 | storiesOf('UI/useCss', module)
19 | .add('Docs', () => )
20 | .add('Demo', () => );
21 |
--------------------------------------------------------------------------------
/stories/useCustomCompareEffect.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useCounter, useCustomCompareEffect } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 | import isDeepEqual from '../src/misc/isDeepEqual';
6 |
7 | const Demo = () => {
8 | const [countNormal, { inc: incNormal }] = useCounter(0);
9 | const [countDeep, { inc: incDeep }] = useCounter(0);
10 | const options = { max: 500 };
11 |
12 | React.useEffect(() => {
13 | if (countNormal < options.max) {
14 | incNormal();
15 | }
16 | }, [options]);
17 |
18 | useCustomCompareEffect(
19 | () => {
20 | if (countNormal < options.max) {
21 | incDeep();
22 | }
23 | },
24 | [options],
25 | (prevDeps, nextDeps) => isDeepEqual(prevDeps, nextDeps)
26 | );
27 |
28 | return (
29 |
30 |
useEffect: {countNormal}
31 |
useCustomCompareEffect: {countDeep}
32 |
33 | );
34 | };
35 |
36 | storiesOf('Lifecycle/useCustomCompareEffect', module)
37 | .add('Docs', () => )
38 | .add('Demo', () => );
39 |
--------------------------------------------------------------------------------
/stories/useDeepCompareEffect.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useCounter, useDeepCompareEffect } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [countNormal, { inc: incNormal }] = useCounter(0);
8 | const [countDeep, { inc: incDeep }] = useCounter(0);
9 | const options = { max: 500 };
10 |
11 | React.useEffect(() => {
12 | if (countNormal < options.max) {
13 | incNormal();
14 | }
15 | }, [options]);
16 |
17 | useDeepCompareEffect(() => {
18 | if (countNormal < options.max) {
19 | incDeep();
20 | }
21 | }, [options]);
22 |
23 | return (
24 |
25 |
useEffect: {countNormal}
26 |
useDeepCompareEffect: {countDeep}
27 |
28 | );
29 | };
30 |
31 | storiesOf('Lifecycle/useDeepCompareEffect', module)
32 | .add('Docs', () => )
33 | .add('Demo', () => );
34 |
--------------------------------------------------------------------------------
/stories/useDefault.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useDefault } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const initialUser = { name: 'Marshall' };
8 | const defaultUser = { name: 'Mathers' };
9 | const [user, setUser] = useDefault(defaultUser, initialUser);
10 |
11 | return (
12 |
13 |
User: {user.name}
14 |
setUser({ name: e.target.value })} />
15 |
setUser(null)}>set to null
16 |
17 | );
18 | };
19 |
20 | storiesOf('State/useDefault', module)
21 | .add('Docs', () => )
22 | .add('Demo', () => );
23 |
--------------------------------------------------------------------------------
/stories/useEffectOnce.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useEffectOnce } from '../src';
4 | import ConsoleStory from './util/ConsoleStory';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | useEffectOnce(() => {
9 | console.log('Running effect once on mount');
10 |
11 | return () => {
12 | console.log('Running clean-up of effect on unmount');
13 | };
14 | });
15 |
16 | return ;
17 | };
18 |
19 | storiesOf('Lifecycle/useEffectOnce', module)
20 | .add('Docs', () => )
21 | .add('Demo', () => );
22 |
--------------------------------------------------------------------------------
/stories/useEvent.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useEvent, useList } from '../src';
4 | import { CenterStory } from './util/CenterStory';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const { useCallback } = React;
8 |
9 | const Demo = () => {
10 | const [list, { push, clear }] = useList();
11 |
12 | const onKeyDown = useCallback(({ key }) => {
13 | if (key === 'r') {
14 | clear();
15 | }
16 | push(key);
17 | }, []);
18 |
19 | useEvent('keydown', onKeyDown);
20 |
21 | return (
22 |
23 |
24 | Press some keys on your keyboard, r
key resets the
25 | list
26 |
27 | {JSON.stringify(list, null, 4)}
28 |
29 | );
30 | };
31 |
32 | storiesOf('Sensors/useEvent', module)
33 | .add('Docs', () => )
34 | .add('Demo', () => );
35 |
--------------------------------------------------------------------------------
/stories/useFavicon.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useFavicon } from '../src';
4 | import NewTabStory from './util/NewTabStory';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | useFavicon('https://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico');
9 |
10 | return Favicon should be the Stack Overflow logo ;
11 | };
12 |
13 | storiesOf('Side effects/useFavicon', module)
14 | .add('Docs', () => )
15 | .add('Demo', () => );
16 |
--------------------------------------------------------------------------------
/stories/useFirstMountState.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useFirstMountState } from '../src/useFirstMountState';
4 | import useUpdate from '../src/useUpdate';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | const isFirstMount = useFirstMountState();
9 | const update = useUpdate();
10 |
11 | return (
12 |
13 | This component is just mounted: {isFirstMount ? 'YES' : 'NO'}
14 |
15 | re-render
16 |
17 | );
18 | };
19 |
20 | storiesOf('State/useFirstMountState', module)
21 | .add('Docs', () => )
22 | .add('Demo', () => );
23 |
--------------------------------------------------------------------------------
/stories/useGeolocation.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useGeolocation } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const state = useGeolocation();
8 |
9 | return {JSON.stringify(state, null, 2)} ;
10 | };
11 |
12 | storiesOf('Sensors/useGeolocation', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/useGetSet.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useState } from 'react';
4 | import { useGetSet } from '../src';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | const [get, set] = useGetSet(0);
9 | const onClick = () => {
10 | setTimeout(() => {
11 | set(get() + 1);
12 | }, 1_000);
13 | };
14 |
15 | return Clicked: {get()} ;
16 | };
17 |
18 | const DemoWrong = () => {
19 | const [cnt, set] = useState(0);
20 | const onClick = () => {
21 | setTimeout(() => {
22 | set(cnt + 1);
23 | }, 1_000);
24 | };
25 |
26 | return Clicked: {cnt} ;
27 | };
28 |
29 | storiesOf('State/useGetSet', module)
30 | .add('Docs', () => )
31 | .add('Demo, 1s delay', () => )
32 | .add('DemoWrong, 1s delay', () => );
33 |
--------------------------------------------------------------------------------
/stories/useGetSetState.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useGetSetState } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [get, setState] = useGetSetState<{ cnt: number }>({ cnt: 0 });
8 | const onClick = () => {
9 | setTimeout(() => {
10 | setState({ cnt: get().cnt + 1 });
11 | }, 1_000);
12 | };
13 |
14 | return Clicked: {get().cnt} ;
15 | };
16 |
17 | storiesOf('State/useGetSetState', module)
18 | .add('Docs', () => )
19 | .add('Demo', () => );
20 |
--------------------------------------------------------------------------------
/stories/useHash.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useHash, useMount } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [hash, setHash] = useHash();
8 |
9 | useMount(() => {
10 | setHash('#/path/to/page?userId=123');
11 | });
12 |
13 | return (
14 |
15 |
window.location.href:
16 |
17 |
{window.location.href}
18 |
19 |
Edit hash:
20 |
21 | setHash(e.target.value)} />
22 |
23 |
24 | );
25 | };
26 |
27 | storiesOf('Sensors/useHash', module)
28 | .add('Docs', () => )
29 | .add('Demo', () => );
30 |
--------------------------------------------------------------------------------
/stories/useHover.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useHover } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const element = (hasHovered: boolean) => Hover me! {hasHovered && 'Thanks!'}
;
8 | const [hoverable, hovered] = useHover(element);
9 |
10 | return (
11 |
12 | {hoverable}
13 |
{hovered ? 'HOVERED' : ''}
14 |
15 | );
16 | };
17 |
18 | storiesOf('Sensors/useHover', module)
19 | .add('Docs', () => )
20 | .add('Demo', () => );
21 |
--------------------------------------------------------------------------------
/stories/useHoverDirty.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useRef } from 'react';
4 | import { useHoverDirty } from '../src';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | const ref = useRef(null);
9 | const isHovered = useHoverDirty(ref);
10 |
11 | return {isHovered ? '😁' : '☹️'}
;
12 | };
13 |
14 | storiesOf('Sensors/useHoverDirty', module)
15 | .add('Docs', () => )
16 | .add('Demo', () => );
17 |
--------------------------------------------------------------------------------
/stories/useIdle.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useIdle } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [idleDelay, setIdleDelay] = React.useState(3e3);
8 | const isIdle = useIdle(idleDelay);
9 |
10 | return (
11 |
12 | Idle delay ms:{' '}
13 |
setIdleDelay(+target.value)}
17 | />
18 |
User is idle: {isIdle ? 'Yes' : 'No'}
19 |
20 | );
21 | };
22 |
23 | storiesOf('Sensors/useIdle', module)
24 | .add('Docs', () => )
25 | .add('Demo', () => );
26 |
--------------------------------------------------------------------------------
/stories/useInterval.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useInterval, useBoolean } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [count, setCount] = React.useState(0);
8 | const [delay, setDelay] = React.useState(1000);
9 | const [isRunning, toggleIsRunning] = useBoolean(true);
10 |
11 | useInterval(
12 | () => {
13 | setCount(count + 1);
14 | },
15 | isRunning ? delay : null
16 | );
17 |
18 | return (
19 |
20 |
21 | delay: setDelay(Number(event.target.value))} />
22 |
23 |
count: {count}
24 |
25 | {isRunning ? 'stop' : 'start'}
26 |
27 |
28 | );
29 | };
30 |
31 | storiesOf('Animation/useInterval', module)
32 | .add('Docs', () => )
33 | .add('Demo', () => );
34 |
--------------------------------------------------------------------------------
/stories/useIsomorphicLayoutEffect.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import ShowDocs from './util/ShowDocs';
4 |
5 | storiesOf('Lifecycle/useIsomorphicLayoutEffect', module).add('Docs', () => (
6 |
7 | ));
8 |
--------------------------------------------------------------------------------
/stories/useKeyPress.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useKeyPress } from '../src';
4 | import { CenterStory } from './util/CenterStory';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
8 |
9 | const Demo = () => {
10 | const states: boolean[] = [];
11 | for (const key of keys) {
12 | // eslint-disable-next-line react-hooks/rules-of-hooks
13 | states.push(useKeyPress(key)[0]);
14 | }
15 |
16 | return (
17 |
18 |
19 | Try pressing numbers
20 |
21 | {states.reduce(
22 | (s, pressed, index) => s + (pressed ? (s ? ' + ' : '') + keys[index] : ''),
23 | ''
24 | )}
25 |
26 |
27 | );
28 | };
29 |
30 | storiesOf('Sensors/useKeyPress', module)
31 | .add('Docs', () => )
32 | .add('Demo', () => );
33 |
--------------------------------------------------------------------------------
/stories/useKeyboardJs.story.tsx:
--------------------------------------------------------------------------------
1 | import { text, withKnobs } from '@storybook/addon-knobs';
2 | import { storiesOf } from '@storybook/react';
3 | import * as React from 'react';
4 | import useKeyboardJs from '../src/useKeyboardJs';
5 | import { CenterStory } from './util/CenterStory';
6 | import ShowDocs from './util/ShowDocs';
7 |
8 | const Demo = ({ combo }) => {
9 | const [pressed] = useKeyboardJs(combo);
10 |
11 | return (
12 |
13 |
14 | Press{' '}
15 |
17 | {combo}
18 |
{' '}
19 | combo
20 |
21 |
22 |
{pressed ? '💋' : ''}
23 |
24 |
25 | );
26 | };
27 |
28 | storiesOf('Sensors/useKeyboardJs', module)
29 | .addDecorator(withKnobs)
30 | .add('Docs', () => )
31 | .add('Demo', () => {
32 | const combo = text('Combo', 'i + l + u');
33 | return ;
34 | });
35 |
--------------------------------------------------------------------------------
/stories/useLatest.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useLatest } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [count, setCount] = React.useState(0);
8 | const latestCount = useLatest(count);
9 | const timeoutMs = 3000;
10 |
11 | function handleAlertClick() {
12 | setTimeout(() => {
13 | alert(`Latest count value: ${latestCount.current}`);
14 | }, timeoutMs);
15 | }
16 |
17 | return (
18 |
19 |
You clicked {count} times
20 |
setCount(count + 1)}>Click me
21 |
Show alert in {timeoutMs / 1000}s
22 |
23 | );
24 | };
25 |
26 | storiesOf('State/useLatest', module)
27 | .add('Docs', () => )
28 | .add('Demo', () => );
29 |
--------------------------------------------------------------------------------
/stories/useLifecycles.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useLifecycles } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | useLifecycles(
8 | () => console.log('MOUNTED'),
9 | () => console.log('UNMOUNTED')
10 | );
11 | return null;
12 | };
13 |
14 | storiesOf('Lifecycle/useLifecycles', module)
15 | .add('Docs', () => )
16 | .add('Demo', () => );
17 |
--------------------------------------------------------------------------------
/stories/useLocalStorage.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useLocalStorage } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [value, setValue] = useLocalStorage('hello-key', 'foo');
8 | const [removableValue, setRemovableValue, remove] = useLocalStorage('removeable-key');
9 |
10 | return (
11 |
12 |
Value: {value}
13 |
setValue('bar')}>bar
14 |
setValue('baz')}>baz
15 |
16 |
17 |
Removable Value: {removableValue}
18 |
setRemovableValue('foo')}>foo
19 |
setRemovableValue('bar')}>bar
20 |
remove()}>Remove
21 |
22 | );
23 | };
24 |
25 | storiesOf('Side effects/useLocalStorage', module)
26 | .add('Docs', () => )
27 | .add('Demo', () => );
28 |
--------------------------------------------------------------------------------
/stories/useLocation.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useLocation } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const go = (page) => window.history.pushState({}, '', page);
7 |
8 | const Demo = () => {
9 | const state = useLocation();
10 |
11 | return (
12 |
13 |
go('page-1')}>Page 1
14 |
go('page-2')}>Page 2
15 |
{JSON.stringify(state, null, 2)}
16 |
17 | );
18 | };
19 |
20 | storiesOf('Sensors/useLocation', module)
21 | .add('Docs', () => )
22 | .add('Demo', () => );
23 |
--------------------------------------------------------------------------------
/stories/useLogger.story.tsx:
--------------------------------------------------------------------------------
1 | import { boolean, text, withKnobs } from '@storybook/addon-knobs';
2 | import { storiesOf } from '@storybook/react';
3 | import * as React from 'react';
4 | import { useCounter, useLogger } from '../src';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = (props) => {
8 | const [state, { inc }] = useCounter(0);
9 |
10 | useLogger('Demo', props, state);
11 |
12 | return (
13 | <>
14 | {props.title}
15 | inc()}>Update state ({state})
16 | >
17 | );
18 | };
19 |
20 | storiesOf('Lifecycle/useLogger', module)
21 | .addDecorator(withKnobs)
22 | .add('Docs', () => )
23 | .add('Demo', () => {
24 | const props = {
25 | title: text('title', 'Open the developer console to see logs'),
26 | bold: boolean('bold', false),
27 | };
28 |
29 | return ;
30 | });
31 |
--------------------------------------------------------------------------------
/stories/useLongPress.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useLongPress } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const onLongPress = () => {
8 | console.log('calls callback after long pressing 300ms');
9 | };
10 |
11 | const defaultOptions = {
12 | isPreventDefault: true,
13 | delay: 300,
14 | };
15 | const longPressEvent = useLongPress(onLongPress, defaultOptions);
16 |
17 | return useLongPress ;
18 | };
19 |
20 | storiesOf('Sensors/useLongPress', module)
21 | .add('Docs', () => )
22 | .add('Demo', () => );
23 |
--------------------------------------------------------------------------------
/stories/useMap.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMap } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [map, { set, remove, reset }] = useMap({
8 | hello: 'there',
9 | });
10 |
11 | return (
12 |
13 |
set(String(Date.now()), new Date().toJSON())}>Add
14 |
reset()}>Reset
15 |
remove('hello')} disabled={!map.hello}>
16 | Remove 'hello'
17 |
18 |
{JSON.stringify(map, null, 2)}
19 |
20 | );
21 | };
22 |
23 | storiesOf('State/useMap', module)
24 | .add('Docs', () => )
25 | .add('Demo', () => );
26 |
--------------------------------------------------------------------------------
/stories/useMeasure.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import React from 'react';
3 | import { useMeasure } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [ref, state] = useMeasure();
8 |
9 | return (
10 | <>
11 | {JSON.stringify(state, null, 2)}
12 |
13 | resize me
14 |
15 | >
16 | );
17 | };
18 |
19 | storiesOf('Sensors/useMeasure', module)
20 | .add('Docs', () => )
21 | .add('Demo', () => );
22 |
--------------------------------------------------------------------------------
/stories/useMedia.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMedia } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const isWide = useMedia('(min-width: 480px)');
8 |
9 | return Screen is wide: {isWide ? 'Yes' : 'No'}
;
10 | };
11 |
12 | storiesOf('Sensors/useMedia', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/useMediaDevices.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMediaDevices } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const state = useMediaDevices();
8 |
9 | return {JSON.stringify(state, null, 2)} ;
10 | };
11 |
12 | storiesOf('Sensors/useMediaDevices', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/useMediatedState.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMediatedState } from '../src/useMediatedState';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const inputMediator = (s) => s.replace(/[\s]+/g, ' ');
7 | const Demo = () => {
8 | const [state, setState] = useMediatedState(inputMediator, '');
9 |
10 | return (
11 |
12 |
You will not be able to enter more than one space
13 |
) => {
19 | setState(ev.target.value);
20 | }}
21 | />
22 |
23 | );
24 | };
25 |
26 | storiesOf('State/useMediatedState', module)
27 | .add('Docs', () => )
28 | .add('Demo', () => );
29 |
--------------------------------------------------------------------------------
/stories/useMethods.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMethods } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const initialState = {
7 | count: 0,
8 | };
9 |
10 | function createMethods(state) {
11 | return {
12 | reset() {
13 | return initialState;
14 | },
15 | increment() {
16 | return { ...state, count: state.count + 1 };
17 | },
18 | decrement() {
19 | return { ...state, count: state.count - 1 };
20 | },
21 | };
22 | }
23 |
24 | const Demo = () => {
25 | const [state, methods] = useMethods(createMethods, initialState);
26 |
27 | return (
28 | <>
29 | Count: {state.count}
30 | -
31 | +
32 | >
33 | );
34 | };
35 |
36 | storiesOf('State/useMethods', module)
37 | .add('Docs', () => )
38 | .add('Demo', () => );
39 |
--------------------------------------------------------------------------------
/stories/useMotion.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMotion } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const state = useMotion();
8 |
9 | return {JSON.stringify(state, null, 2)} ;
10 | };
11 |
12 | storiesOf('Sensors/useMotion', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/useMount.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMount } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | useMount(() => alert('MOUNTED'));
8 |
9 | return (
10 |
11 | useMount()
hook can be used to perform a side-effect when component is mounted.
12 |
13 | );
14 | };
15 |
16 | storiesOf('Lifecycle/useMount', module)
17 | .add('Docs', () => )
18 | .add('Demo', () => );
19 |
--------------------------------------------------------------------------------
/stories/useMountedState.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMountedState } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const isMounted = useMountedState();
8 | const [, updateState] = React.useState();
9 |
10 | requestAnimationFrame(updateState);
11 |
12 | return This component is {isMounted() ? 'MOUNTED' : 'NOT MOUNTED'}
;
13 | };
14 |
15 | storiesOf('Lifecycle/useMountedState', module)
16 | .add('Docs', () => )
17 | .add('Demo', () => );
18 |
--------------------------------------------------------------------------------
/stories/useMouse.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMouse } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const ref = React.useRef(null);
8 | const state = useMouse(ref);
9 |
10 | return (
11 | <>
12 | {JSON.stringify(state, null, 2)}
13 |
14 |
15 |
23 |
31 | 🐭
32 |
33 |
34 | >
35 | );
36 | };
37 |
38 | storiesOf('Sensors/useMouse', module)
39 | .add('Docs', () => )
40 | .add('Demo', () => );
41 |
--------------------------------------------------------------------------------
/stories/useMouseWheel.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useMouseWheel } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const mouseWheel = useMouseWheel();
8 | return (
9 | <>
10 | delta Y Scrolled: {mouseWheel}
11 | >
12 | );
13 | };
14 |
15 | storiesOf('Sensors/useMouseWheel', module)
16 | .add('Docs', () => )
17 | .add('Demo', () => );
18 |
--------------------------------------------------------------------------------
/stories/useNetwork.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useEffect } from 'react';
4 | import { useNetworkState } from '../src';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | const state = useNetworkState();
9 |
10 | useEffect(() => {
11 | console.log(state);
12 | }, [state]);
13 |
14 | return (
15 |
16 |
Since JSON do not output `undefined` fields look the console to see whole the state
17 |
{JSON.stringify(state, null, 2)}
18 |
19 | );
20 | };
21 |
22 | storiesOf('Sensors/useNetworkState', module)
23 | .add('Docs', () => )
24 | .add('Demo', () => );
25 |
--------------------------------------------------------------------------------
/stories/useObservable.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { BehaviorSubject } from 'rxjs';
4 | import { useObservable } from '../src';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const counter$ = new BehaviorSubject(0);
8 | const Demo = () => {
9 | const value = useObservable(counter$, 0);
10 |
11 | return counter$.next(value! + 1)}>Clicked {value} times ;
12 | };
13 |
14 | storiesOf('State/useObservable', module)
15 | .add('Docs', () => )
16 | .add('Demo', () => );
17 |
--------------------------------------------------------------------------------
/stories/useOrientation.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useOrientation } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const state = useOrientation();
8 |
9 | return {JSON.stringify(state, null, 2)} ;
10 | };
11 |
12 | storiesOf('Sensors/useOrientation', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/usePageLeave.story.tsx:
--------------------------------------------------------------------------------
1 | import { action } from '@storybook/addon-actions';
2 | import { storiesOf } from '@storybook/react';
3 | import * as React from 'react';
4 | import { usePageLeave } from '../src';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | usePageLeave(action('onPageLeave'));
9 |
10 | return (
11 |
12 | Try leaving the page and see logs in Actions
tab.
13 |
14 | );
15 | };
16 |
17 | storiesOf('Sensors/usePageLeave', module)
18 | .add('Docs', () => )
19 | .add('Default', () => );
20 |
--------------------------------------------------------------------------------
/stories/usePermission.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { usePermission } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const state = usePermission({ name: 'microphone' });
8 |
9 | return {JSON.stringify(state, null, 2)} ;
10 | };
11 |
12 | storiesOf('Side effects/usePermission', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/usePrevious.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { usePrevious } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [count, setCount] = React.useState(0);
8 | const prevCount = usePrevious(count);
9 |
10 | return (
11 |
12 |
13 | Now: {count}, before: {String(prevCount)}
14 |
15 |
setCount((value) => value + 1)}>+
16 |
setCount((value) => value - 1)}>-
17 |
18 | );
19 | };
20 |
21 | storiesOf('State/usePrevious', module)
22 | .add('Docs', () => )
23 | .add('Demo', () => );
24 |
--------------------------------------------------------------------------------
/stories/usePreviousDistinct.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { usePreviousDistinct, useCounter } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [count, { inc: relatedInc }] = useCounter(0);
8 | const [unrelatedCount, { inc }] = useCounter(0);
9 | const prevCount = usePreviousDistinct(count);
10 |
11 | return (
12 |
13 | Now: {count}, before: {prevCount}
14 | relatedInc()}>Increment
15 | Unrelated: {unrelatedCount}
16 | inc()}>Increment Unrelated
17 |
18 | );
19 | };
20 |
21 | storiesOf('State/usePreviousDistinct', module)
22 | .add('Docs', () => )
23 | .add('Demo', () => );
24 |
--------------------------------------------------------------------------------
/stories/useQueue.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useQueue } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const { add, remove, first, last, size } = useQueue();
8 | return (
9 |
10 |
11 | first: {first}
12 | last: {last}
13 | size: {size}
14 |
15 |
add((last || 0) + 1)}>Add
16 |
remove()}>Remove
17 |
18 | );
19 | };
20 |
21 | storiesOf('State/useQueue', module)
22 | .add('Docs', () => )
23 | .add('Demo', () => );
24 |
--------------------------------------------------------------------------------
/stories/useRaf.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useRaf } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const frames = useRaf(5000, 1000);
8 |
9 | return Elapsed: {frames}
;
10 | };
11 |
12 | storiesOf('Animation/useRaf', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/useRafLoop.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useRafLoop, useUpdate } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [ticks, setTicks] = React.useState(0);
8 | const [lastCall, setLastCall] = React.useState(0);
9 | const update = useUpdate();
10 |
11 | const [loopStop, loopStart, isActive] = useRafLoop((time) => {
12 | setTicks((ticks) => ticks + 1);
13 | setLastCall(time);
14 | });
15 |
16 | return (
17 |
18 |
RAF triggered: {ticks} (times)
19 |
Last high res timestamp: {lastCall}
20 |
21 |
{
23 | isActive() ? loopStop() : loopStart();
24 | update();
25 | }}>
26 | {isActive() ? 'STOP' : 'START'}
27 |
28 |
29 | );
30 | };
31 |
32 | storiesOf('Side effects/useRafLoop', module)
33 | .add('Docs', () => )
34 | .add('Demo', () => );
35 |
--------------------------------------------------------------------------------
/stories/useRafState.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useRafState, useMount } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [state, setState] = useRafState({ x: 0, y: 0 });
8 |
9 | useMount(() => {
10 | const onMouseMove = (event: MouseEvent) => {
11 | setState({ x: event.clientX, y: event.clientY });
12 | };
13 | const onTouchMove = (event: TouchEvent) => {
14 | setState({ x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY });
15 | };
16 |
17 | document.addEventListener('mousemove', onMouseMove);
18 | document.addEventListener('touchmove', onTouchMove);
19 |
20 | return () => {
21 | document.removeEventListener('mousemove', onMouseMove);
22 | document.removeEventListener('touchmove', onTouchMove);
23 | };
24 | });
25 |
26 | return {JSON.stringify(state, null, 2)} ;
27 | };
28 |
29 | storiesOf('State/useRafState', module)
30 | .add('Docs', () => )
31 | .add('Demo', () => );
32 |
--------------------------------------------------------------------------------
/stories/useRendersCount.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useRendersCount } from '../src/useRendersCount';
4 | import useUpdate from '../src/useUpdate';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | const update = useUpdate();
9 | const rendersCount = useRendersCount();
10 |
11 | return (
12 |
13 | Renders count: {rendersCount}
14 |
15 | re-render
16 |
17 | );
18 | };
19 |
20 | storiesOf('State/useRendersCount', module)
21 | .add('Docs', () => )
22 | .add('Demo', () => );
23 |
--------------------------------------------------------------------------------
/stories/useScroll.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useScroll } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const scrollRef = React.useRef(null);
8 | const { x, y } = useScroll(scrollRef);
9 |
10 | return (
11 | <>
12 | x: {x}
13 | y: {y}
14 |
24 | >
25 | );
26 | };
27 |
28 | storiesOf('Sensors/useScroll', module)
29 | .add('Docs', () => )
30 | .add('Demo', () => );
31 |
--------------------------------------------------------------------------------
/stories/useScrollbarWidth.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useScrollbarWidth } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const sbw = useScrollbarWidth();
8 |
9 | return (
10 |
11 | {sbw === undefined
12 | ? `DOM is not ready yet, SBW detection delayed`
13 | : `Browser's scrollbar width is ${sbw}px`}
14 |
15 | );
16 | };
17 |
18 | storiesOf('Sensors/useScrollbarWidth', module)
19 | .add('Docs', () => )
20 | .add('Demo', () => );
21 |
--------------------------------------------------------------------------------
/stories/useScrolling.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useScrolling } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const scrollRef = React.useRef(null);
8 | const scrolling = useScrolling(scrollRef);
9 |
10 | return (
11 | <>
12 | {{scrolling ? 'Scrolling' : 'Not scrolling'}
}
13 |
14 |
24 | >
25 | );
26 | };
27 |
28 | storiesOf('Sensors/useScrolling', module)
29 | .add('Docs', () => )
30 | .add('Demo', () => );
31 |
--------------------------------------------------------------------------------
/stories/useSearchParam.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useSearchParam } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const edit = useSearchParam('edit');
8 |
9 | return (
10 |
11 |
edit: {edit || '🤷♂️'}
12 |
13 | window.history.pushState({}, '', window.location.pathname + '?edit=123')}>
15 | Edit post 123 (?edit=123)
16 |
17 |
18 |
19 | window.history.pushState({}, '', window.location.pathname + '?edit=999')}>
21 | Edit post 999 (?edit=999)
22 |
23 |
24 |
25 | window.history.pushState({}, '', window.location.pathname)}>
26 | Close modal
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | storiesOf('Sensors/useSearchParam', module)
34 | .add('Docs', () => )
35 | .add('Demo', () => );
36 |
--------------------------------------------------------------------------------
/stories/useSessionStorage.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useSessionStorage } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [value, setValue] = useSessionStorage('hello-key', 'foo');
8 |
9 | return (
10 |
11 |
Value: {value}
12 |
setValue('bar')}>bar
13 |
setValue('baz')}>baz
14 |
15 | );
16 | };
17 |
18 | storiesOf('Side effects/useSessionStorage', module)
19 | .add('Docs', () => )
20 | .add('Demo', () => );
21 |
--------------------------------------------------------------------------------
/stories/useSet.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useSet } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [set, { add, has, remove, reset, clear, toggle }] = useSet(new Set(['hello']));
8 |
9 | return (
10 |
11 |
add(String(Date.now()))}>Add
12 |
reset()}>Reset
13 |
clear()}>Clear
14 |
remove('hello')} disabled={!has('hello')}>
15 | Remove 'hello'
16 |
17 |
toggle('hello')}>Toggle 'hello'
18 |
{JSON.stringify(Array.from(set), null, 2)}
19 |
20 | );
21 | };
22 |
23 | storiesOf('State/useSet', module)
24 | .add('Docs', () => )
25 | .add('Demo', () => );
26 |
--------------------------------------------------------------------------------
/stories/useSetState.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useSetState } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [state, setState] = useSetState({});
8 |
9 | return (
10 |
11 |
{JSON.stringify(state, null, 2)}
12 |
setState({ hello: 'world' })}>hello
13 |
setState({ foo: 'bar' })}>foo
14 |
{
16 | setState((prevState) => ({
17 | count: prevState.count === undefined ? 0 : prevState.count + 1,
18 | }));
19 | }}>
20 | increment
21 |
22 |
23 | );
24 | };
25 |
26 | storiesOf('State/useSetState', module)
27 | .add('Docs', () => )
28 | .add('Demo', () => );
29 |
--------------------------------------------------------------------------------
/stories/useSize.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useSize } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [sized, state] = useSize(({ width: currentWidth }) => (
8 | Size me up! ({currentWidth}px)
9 | ));
10 |
11 | return (
12 |
13 |
{JSON.stringify(state, null, 2)}
14 | {sized}
15 |
16 | );
17 | };
18 |
19 | storiesOf('Sensors/useSize', module)
20 | .add('Docs', () => )
21 | .add('Demo', () => );
22 |
--------------------------------------------------------------------------------
/stories/useSpeech.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useSpeech } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const voices = window.speechSynthesis.getVoices();
8 | const state = useSpeech('Hello world!', { rate: 0.8, pitch: 0.5, voice: voices[0] });
9 |
10 | return {JSON.stringify(state, null, 2)} ;
11 | };
12 |
13 | storiesOf('UI/useSpeech', module)
14 | .add('Docs', () => )
15 | .add('Demo', () => );
16 |
--------------------------------------------------------------------------------
/stories/useSpring.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import useSpring from '../src/useSpring';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [target, setTarget] = (React as any).useState(50);
8 | const value = useSpring(target);
9 |
10 | return (
11 |
12 | {value}
13 |
14 | setTarget(0)}>Set 0
15 | setTarget(100)}>Set 100
16 |
17 | );
18 | };
19 |
20 | storiesOf('Animation/useSpring', module)
21 | .add('Docs', () => )
22 | .add('Demo', () => );
23 |
--------------------------------------------------------------------------------
/stories/useStartTyping.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useStartTyping } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const input = React.useRef(null);
8 | useStartTyping(() => {
9 | if (input.current) {
10 | input.current.focus();
11 | }
12 | });
13 |
14 | return (
15 |
16 |
Start typing, and below field will get focused.
17 |
18 |
19 |
20 |
21 |
22 |
Try focusing below elements and see what happens.
23 |
When button is focused, it will lose it.
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Editable DIV
33 |
34 | );
35 | };
36 |
37 | storiesOf('Sensors/useStartTyping', module)
38 | .add('Docs', () => )
39 | .add('Demo', () => );
40 |
--------------------------------------------------------------------------------
/stories/useStateValidator.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import useStateValidator from '../src/useStateValidator';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const DemoStateValidator = (s) =>
7 | [s === '' ? undefined : (s * 1) % 2 === 0] as [boolean | undefined];
8 | const Demo = () => {
9 | const [state, setState] = React.useState(0);
10 | const [[isValid]] = useStateValidator(state, DemoStateValidator);
11 |
12 | return (
13 |
14 |
Below field is valid only if number is even
15 |
) => {
21 | setState(ev.target.value as unknown as number);
22 | }}
23 | />
24 | {isValid !== undefined &&
{isValid ? 'Valid!' : 'Invalid'} }
25 |
26 | );
27 | };
28 |
29 | storiesOf('State/useStateValidator', module)
30 | .add('Docs', () => )
31 | .add('Demo', () => );
32 |
--------------------------------------------------------------------------------
/stories/useTimeout.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useTimeout } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | function TestComponent(props: { ms?: number } = {}) {
7 | const ms = props.ms || 5000;
8 | const [isReady, cancel] = useTimeout(ms);
9 |
10 | return (
11 |
12 | {isReady() ? "I'm reloaded after timeout" : `I will be reloaded after ${ms / 1000}s`}
13 | {isReady() === false ? Cancel : ''}
14 |
15 | );
16 | }
17 |
18 | const Demo = () => {
19 | return (
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | storiesOf('Animation/useTimeout', module)
28 | .add('Docs', () => )
29 | .add('Demo', () => );
30 |
--------------------------------------------------------------------------------
/stories/useTitle.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useTitle } from '../src';
4 | import NewTabStory from './util/NewTabStory';
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | useTitle('Hello world!');
9 |
10 | return Title should be "Hello world!" ;
11 | };
12 |
13 | storiesOf('Side effects/useTitle', module)
14 | .add('Docs', () => )
15 | .add('Demo', () => );
16 |
--------------------------------------------------------------------------------
/stories/useToggle.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useToggle } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [on, toggle] = useToggle(true);
8 |
9 | return (
10 |
11 |
{on ? 'ON' : 'OFF'}
12 |
Toggle
13 |
toggle(true)}>set ON
14 |
toggle(false)}>set OFF
15 |
16 | );
17 | };
18 |
19 | storiesOf('State/useToggle', module)
20 | .add('Docs', () => )
21 | .add('Demo', () => );
22 |
--------------------------------------------------------------------------------
/stories/useTween.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useTween } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const t = useTween();
8 |
9 | return Tween: {t}
;
10 | };
11 |
12 | storiesOf('Animation/useTween', module)
13 | .add('Docs', () => )
14 | .add('Demo', () => );
15 |
--------------------------------------------------------------------------------
/stories/useUnmount.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useUnmount } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | useUnmount(() => alert('UNMOUNTED'));
8 |
9 | return (
10 |
11 | useUnmount()
hook can be used to perform side-effects when component unmounts.
12 | This component will alert you when it is un-mounted.
13 |
14 | );
15 | };
16 |
17 | storiesOf('Lifecycle/useUnmount', module)
18 | .add('Docs', () => )
19 | .add('Demo', () => );
20 |
--------------------------------------------------------------------------------
/stories/useUpdate.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useUpdate } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const update = useUpdate();
8 | return (
9 | <>
10 | Time: {Date.now()}
11 | Update
12 | >
13 | );
14 | };
15 |
16 | storiesOf('Animation/useUpdate', module)
17 | .add('Docs', () => )
18 | .add('Demo', () => );
19 |
--------------------------------------------------------------------------------
/stories/useUpdateEffect.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useUpdateEffect } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [count, setCount] = React.useState(0);
8 | const [didUpdate, setDidUpdate] = React.useState(false);
9 |
10 | useUpdateEffect(() => {
11 | setDidUpdate(true);
12 | }, [count]);
13 |
14 | return (
15 |
16 |
setCount((currentCount) => currentCount + 1)}>Count: {count}
17 |
Updated: {didUpdate}
18 |
19 | );
20 | };
21 |
22 | storiesOf('Lifecycle/useUpdateEffect', module)
23 | .add('Docs', () => )
24 | .add('Demo', () => );
25 |
--------------------------------------------------------------------------------
/stories/useVibrate.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useVibrate, useToggle } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const [vibrating, toggleVibrating] = useToggle(false);
8 |
9 | useVibrate(vibrating, [300, 100, 200, 100, 1000, 300]);
10 |
11 | return (
12 |
13 | {vibrating ? 'Stop' : 'Vibrate'}
14 |
15 | );
16 | };
17 |
18 | storiesOf('UI/useVibrate', module)
19 | .add('Docs', () => )
20 | .add('Demo', () => );
21 |
--------------------------------------------------------------------------------
/stories/useWindowScroll.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useWindowScroll } from '../src';
4 | import ShowDocs from './util/ShowDocs';
5 |
6 | const Demo = () => {
7 | const { x, y } = useWindowScroll();
8 |
9 | return (
10 |
15 |
21 |
x: {x}
22 |
y: {y}
23 |
24 |
25 | );
26 | };
27 |
28 | storiesOf('Sensors/useWindowScroll', module)
29 | .add('Docs', () => )
30 | .add('Demo', () => );
31 |
--------------------------------------------------------------------------------
/stories/useWindowSize.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import { useWindowSize } from '../src';
4 | import { action } from '@storybook/addon-actions'; // Import addon-actions
5 | import ShowDocs from './util/ShowDocs';
6 |
7 | const Demo = () => {
8 | const { width, height } = useWindowSize({
9 | // Log the resize event to the Storybook actions panel
10 | onChange: action('window resize'),
11 | });
12 |
13 | return (
14 |
15 |
width: {width}
16 |
height: {height}
17 |
18 | );
19 | };
20 |
21 | storiesOf('Sensors/useWindowSize', module)
22 | .add('Docs', () => )
23 | .add('Demo', () => );
24 |
--------------------------------------------------------------------------------
/stories/util/CenterStory.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export const CenterStory = ({ children }) => (
4 |
14 | );
15 |
--------------------------------------------------------------------------------
/stories/util/ConsoleStory.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const ConsoleStory = ({ message = 'Open the developer console to see logs' }) => {message}
;
4 |
5 | export default ConsoleStory;
6 |
--------------------------------------------------------------------------------
/stories/util/NewTabStory.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const NewTabStory = ({ children }) => {
4 | if (window.location === window.parent.location) {
5 | return children;
6 | }
7 |
8 | return (
9 |
10 | This story should be{' '}
11 |
12 | opened in a new tab
13 |
14 | .
15 |
16 | );
17 | };
18 |
19 | export default NewTabStory;
20 |
--------------------------------------------------------------------------------
/tests/misc/hookState.test.ts:
--------------------------------------------------------------------------------
1 | import { resolveHookState } from '../../src/misc/hookState';
2 |
3 | describe('resolveHookState', () => {
4 | it('should defined', () => {
5 | expect(resolveHookState).toBeDefined();
6 | });
7 |
8 | it(`should return value as is if it's not a function`, () => {
9 | expect(resolveHookState(1)).toBe(1);
10 | expect(resolveHookState('HI!')).toBe('HI!');
11 | expect(resolveHookState(undefined)).toBe(undefined);
12 | });
13 |
14 | it('should call passed function', () => {
15 | const spy = jest.fn();
16 | resolveHookState(spy);
17 | expect(spy).toHaveBeenCalled();
18 | });
19 |
20 | it('should pass 2nd parameter to function if it awaited', () => {
21 | const spy = jest.fn((n: number) => n);
22 | resolveHookState(spy, 123);
23 | expect(spy).toHaveBeenCalled();
24 | expect(spy.mock.calls[0][0]).toBe(123);
25 | });
26 |
27 | it('should not pass 2nd parameter to function if it not awaited', () => {
28 | const spy = jest.fn(() => {});
29 | /* @ts-expect-error */
30 | resolveHookState(spy, 123);
31 | expect(spy).toHaveBeenCalled();
32 | expect(spy.mock.calls[0].length).toBe(0);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/setupTests.ts:
--------------------------------------------------------------------------------
1 | import 'jest-localstorage-mock';
2 | import { isBrowser } from '../src/misc/util';
3 |
4 | if (isBrowser) {
5 | (window as any).ResizeObserver = class ResizeObserver {
6 | observe() {}
7 |
8 | disconnect() {}
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/tests/useBoolean.test.ts:
--------------------------------------------------------------------------------
1 | import useBoolean from '../src/useBoolean';
2 | import useToggle from '../src/useToggle';
3 |
4 | it('should be an alias for useToggle ', () => {
5 | expect(useBoolean).toBe(useToggle);
6 | });
7 |
--------------------------------------------------------------------------------
/tests/useEffectOnce.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useEffectOnce } from '../src';
3 |
4 | const mockEffectCleanup = jest.fn();
5 | const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup);
6 |
7 | it('should run provided effect only once', () => {
8 | const { rerender } = renderHook(() => useEffectOnce(mockEffectCallback));
9 | expect(mockEffectCallback).toHaveBeenCalledTimes(1);
10 |
11 | rerender();
12 | expect(mockEffectCallback).toHaveBeenCalledTimes(1);
13 | });
14 |
15 | it('should run clean-up provided on unmount', () => {
16 | const { unmount } = renderHook(() => useEffectOnce(mockEffectCallback));
17 | expect(mockEffectCleanup).not.toHaveBeenCalled();
18 |
19 | unmount();
20 | expect(mockEffectCleanup).toHaveBeenCalledTimes(1);
21 | });
22 |
--------------------------------------------------------------------------------
/tests/useError.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook, act } from '@testing-library/react-hooks';
2 | import { useError } from '../src';
3 |
4 | const setup = () => renderHook(() => useError());
5 |
6 | beforeEach(() => {
7 | jest.spyOn(console, 'error').mockImplementation(() => {});
8 | });
9 |
10 | afterEach(() => {
11 | jest.clearAllMocks();
12 | });
13 |
14 | it('should throw an error on error dispatch', () => {
15 | const errorStr = 'some_error';
16 |
17 | try {
18 | const { result } = setup();
19 |
20 | act(() => {
21 | result.current(new Error(errorStr));
22 | });
23 | } catch (err) {
24 | expect(err.message).toEqual(errorStr);
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/tests/useFirstMountState.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useFirstMountState } from '../src';
3 |
4 | describe('useFirstMountState', () => {
5 | it('should be defined', () => {
6 | expect(useFirstMountState).toBeDefined();
7 | });
8 |
9 | it('should return boolean', () => {
10 | expect(renderHook(() => useFirstMountState()).result.current).toEqual(expect.any(Boolean));
11 | });
12 |
13 | it('should return true on first render and false on all others', () => {
14 | const hook = renderHook(() => useFirstMountState());
15 |
16 | expect(hook.result.current).toBe(true);
17 | hook.rerender();
18 | expect(hook.result.current).toBe(false);
19 | hook.rerender();
20 | expect(hook.result.current).toBe(false);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/useLatest.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import useLatest from '../src/useLatest';
3 |
4 | const setUp = () => renderHook(({ state }) => useLatest(state), { initialProps: { state: 0 } });
5 |
6 | it('should return a ref with the latest value on initial render', () => {
7 | const { result } = setUp();
8 |
9 | expect(result.current).toEqual({ current: 0 });
10 | });
11 |
12 | it('should always return a ref with the latest value after each update', () => {
13 | const { result, rerender } = setUp();
14 |
15 | rerender({ state: 2 });
16 | expect(result.current).toEqual({ current: 2 });
17 |
18 | rerender({ state: 4 });
19 | expect(result.current).toEqual({ current: 4 });
20 |
21 | rerender({ state: 6 });
22 | expect(result.current).toEqual({ current: 6 });
23 | });
24 |
--------------------------------------------------------------------------------
/tests/useMount.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useMount } from '../src';
3 |
4 | const mockCallback = jest.fn();
5 |
6 | afterEach(() => {
7 | jest.resetAllMocks();
8 | });
9 |
10 | it('should call provided callback on mount', () => {
11 | renderHook(() => useMount(mockCallback));
12 |
13 | expect(mockCallback).toHaveBeenCalledTimes(1);
14 | });
15 |
16 | it('should not call provided callback on unmount', () => {
17 | const { unmount } = renderHook(() => useMount(mockCallback));
18 | expect(mockCallback).toHaveBeenCalledTimes(1);
19 |
20 | unmount();
21 |
22 | expect(mockCallback).toHaveBeenCalledTimes(1);
23 | });
24 |
25 | it('should not call provided callback on rerender', () => {
26 | const { rerender } = renderHook(() => useMount(mockCallback));
27 | expect(mockCallback).toHaveBeenCalledTimes(1);
28 |
29 | rerender();
30 |
31 | expect(mockCallback).toHaveBeenCalledTimes(1);
32 | });
33 |
--------------------------------------------------------------------------------
/tests/useMountedState.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import useMountedState from '../src/useMountedState';
3 |
4 | describe('useMountedState', () => {
5 | it('should be defined', () => {
6 | expect(useMountedState).toBeDefined();
7 | });
8 |
9 | it('should return a function', () => {
10 | const hook = renderHook(() => useMountedState(), { initialProps: false });
11 |
12 | expect(typeof hook.result.current).toEqual('function');
13 | });
14 |
15 | it('should return true if component is mounted', () => {
16 | const hook = renderHook(() => useMountedState(), { initialProps: false });
17 |
18 | expect(hook.result.current()).toBeTruthy();
19 | });
20 |
21 | it('should return false if component is unmounted', () => {
22 | const hook = renderHook(() => useMountedState(), { initialProps: false });
23 |
24 | hook.unmount();
25 |
26 | expect(hook.result.current()).toBeFalsy();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/useNetworkState.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useNetworkState } from '../src';
3 |
4 | //ToDo: improve tests
5 | describe(`useNetworkState`, () => {
6 | it('should be defined', () => {
7 | expect(useNetworkState).toBeDefined();
8 | });
9 |
10 | it('should return an object of certain structure', () => {
11 | const hook = renderHook(() => useNetworkState(), { initialProps: false });
12 |
13 | expect(typeof hook.result.current).toEqual('object');
14 | expect(Object.keys(hook.result.current)).toEqual([
15 | 'online',
16 | 'previous',
17 | 'since',
18 | 'downlink',
19 | 'downlinkMax',
20 | 'effectiveType',
21 | 'rtt',
22 | 'saveData',
23 | 'type',
24 | ]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/useNumber.test.ts:
--------------------------------------------------------------------------------
1 | import useCounter from '../src/useCounter';
2 | import useNumber from '../src/useNumber';
3 |
4 | it('should be an alias for useCounter', () => {
5 | expect(useNumber).toBe(useCounter);
6 | });
7 |
--------------------------------------------------------------------------------
/tests/usePrevious.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import usePrevious from '../src/usePrevious';
3 |
4 | const setUp = () => renderHook(({ state }) => usePrevious(state), { initialProps: { state: 0 } });
5 |
6 | it('should return undefined on initial render', () => {
7 | const { result } = setUp();
8 |
9 | expect(result.current).toBeUndefined();
10 | });
11 |
12 | it('should always return previous state after each update', () => {
13 | const { result, rerender } = setUp();
14 |
15 | rerender({ state: 2 });
16 | expect(result.current).toBe(0);
17 |
18 | rerender({ state: 4 });
19 | expect(result.current).toBe(2);
20 |
21 | rerender({ state: 6 });
22 | expect(result.current).toBe(4);
23 | });
24 |
--------------------------------------------------------------------------------
/tests/useQueue.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from '@testing-library/react-hooks';
2 | import useQueue from '../src/useQueue';
3 |
4 | const setUp = (initialQueue?: any[]) => renderHook(() => useQueue(initialQueue));
5 |
6 | it('takes initial state', () => {
7 | const { result } = setUp([1, 2, 3]);
8 | const { first, last, size } = result.current;
9 | expect(first).toEqual(1);
10 | expect(last).toEqual(3);
11 | expect(size).toEqual(3);
12 | });
13 |
14 | it('appends new member', () => {
15 | const { result } = setUp([1, 2]);
16 | act(() => {
17 | result.current.add(3);
18 | });
19 | const { first, last, size } = result.current;
20 | expect(first).toEqual(1);
21 | expect(last).toEqual(3);
22 | expect(size).toEqual(3);
23 | });
24 |
25 | it('pops oldest member', () => {
26 | const { result } = setUp([1, 2]);
27 | act(() => {
28 | result.current.remove();
29 | });
30 | const { first, size } = result.current;
31 | expect(first).toEqual(2);
32 | expect(size).toEqual(1);
33 | });
34 |
--------------------------------------------------------------------------------
/tests/useRendersCount.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useRendersCount } from '../src';
3 |
4 | describe('useRendersCount', () => {
5 | it('should be defined', () => {
6 | expect(useRendersCount).toBeDefined();
7 | });
8 |
9 | it('should return number', () => {
10 | expect(renderHook(() => useRendersCount()).result.current).toEqual(expect.any(Number));
11 | });
12 |
13 | it('should return actual number of renders', () => {
14 | const hook = renderHook(() => useRendersCount());
15 |
16 | expect(hook.result.current).toBe(1);
17 | hook.rerender();
18 | expect(hook.result.current).toBe(2);
19 | hook.rerender();
20 | expect(hook.result.current).toBe(3);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/useTitle.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import useTitle from '../src/useTitle';
3 |
4 | describe('useTitle', () => {
5 | it('should be defined', () => {
6 | expect(useTitle).toBeDefined();
7 | });
8 |
9 | it('should update document title', () => {
10 | const hook = renderHook((props) => useTitle(props), { initialProps: 'My page title' });
11 |
12 | expect(document.title).toBe('My page title');
13 | hook.rerender('My other page title');
14 | expect(document.title).toBe('My other page title');
15 | });
16 |
17 | it('should restore document title on unmount', () => {
18 | renderHook((props) => useTitle(props), { initialProps: 'Old Title' });
19 | expect(document.title).toBe('Old Title');
20 |
21 | const hook = renderHook((props) => useTitle(props.title, { restoreOnUnmount: props.restore }), {
22 | initialProps: { title: 'New Title', restore: true },
23 | });
24 | expect(document.title).toBe('New Title');
25 | hook.unmount();
26 | expect(document.title).toBe('Old Title');
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/useUpdateEffect.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useUpdateEffect } from '../src';
3 |
4 | it('should run effect on update', () => {
5 | const effect = jest.fn();
6 |
7 | const { rerender } = renderHook(() => useUpdateEffect(effect));
8 | expect(effect).not.toHaveBeenCalled();
9 |
10 | rerender();
11 | expect(effect).toHaveBeenCalledTimes(1);
12 | });
13 |
14 | it('should run cleanup on unmount', () => {
15 | const cleanup = jest.fn();
16 | const effect = jest.fn().mockReturnValue(cleanup);
17 | const hook = renderHook(() => useUpdateEffect(effect));
18 |
19 | hook.rerender();
20 | hook.unmount();
21 |
22 | expect(cleanup).toHaveBeenCalledTimes(1);
23 | });
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "jsx": "react",
7 | "declaration": true,
8 | "pretty": true,
9 | "rootDir": "src",
10 | "sourceMap": false,
11 | "strict": true,
12 | "esModuleInterop": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "noImplicitAny": false,
17 | "noFallthroughCasesInSwitch": true,
18 | "outDir": "lib",
19 | "lib": [
20 | "es2018",
21 | "dom"
22 | ],
23 | "importHelpers": true
24 | },
25 | "exclude": [
26 | "node_modules",
27 | "lib",
28 | "esm",
29 | "tests",
30 | "stories",
31 | "jest.config.ts",
32 | "jest.config.*.ts"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------