From 641a879a51851127893d9407c1294c0403117daa Mon Sep 17 00:00:00 2001 From: xobotyi Date: Wed, 4 Sep 2019 11:29:26 +0300 Subject: [PATCH 0001/1144] useAsyncFn and useAsync typings made great again =) Now it uses fully generic arguments and returning types, thus no problems with inferring callback call arguments; --- src/useAsync.ts | 11 ++++------- src/useAsyncFn.ts | 23 ++++++++++------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/useAsync.ts b/src/useAsync.ts index e20b0b68b6..9046b31da0 100644 --- a/src/useAsync.ts +++ b/src/useAsync.ts @@ -1,13 +1,10 @@ import { DependencyList, useEffect } from 'react'; -import useAsyncFn from './useAsyncFn'; +import useAsyncFn, { FnReturningPromise } from './useAsyncFn'; -export { AsyncState, AsyncFn } from './useAsyncFn'; +export { AsyncState, AsyncFnReturn } from './useAsyncFn'; -export default function useAsync( - fn: (...args: Args | []) => Promise, - deps: DependencyList = [] -) { - const [state, callback] = useAsyncFn(fn, deps, { +export default function useAsync(fn: T, deps: DependencyList = []) { + const [state, callback] = useAsyncFn(fn, deps, { loading: true, }); diff --git a/src/useAsyncFn.ts b/src/useAsyncFn.ts index dd2fc33e9f..cfdb17b608 100644 --- a/src/useAsyncFn.ts +++ b/src/useAsyncFn.ts @@ -18,21 +18,18 @@ export type AsyncState = value: T; }; -export type AsyncFn = [ - AsyncState, - (...args: Args | []) => Promise -]; +export type FnReturningPromise = (...args: any[]) => Promise; +export type AsyncFnReturn = [AsyncState>, T]; -export default function useAsyncFn( - fn: (...args: Args | []) => Promise, +export default function useAsync( + fn: T, deps: DependencyList = [], - initialState: AsyncState = { loading: false } -): AsyncFn { - const [state, set] = useState>(initialState); - + initialState: AsyncState> = { loading: false } +): AsyncFnReturn { const isMounted = useMountedState(); + const [state, set] = useState>>(initialState); - const callback = useCallback((...args: Args | []) => { + const callback = useCallback((...args: Parameters): ReturnType => { set({ loading: true }); return fn(...args).then( @@ -46,8 +43,8 @@ export default function useAsyncFn( return error; } - ); + ) as ReturnType; }, deps); - return [state, callback]; + return [state, (callback as unknown) as T]; } From 563f71db14d0f2acc7dda8246f6a0ed41ec93d68 Mon Sep 17 00:00:00 2001 From: xobotyi Date: Wed, 4 Sep 2019 13:30:40 +0300 Subject: [PATCH 0002/1144] Fix Promise type detection; Fix function name; --- src/useAsync.ts | 3 ++- src/useAsyncFn.ts | 12 +++++++----- src/util.ts | 4 ++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/useAsync.ts b/src/useAsync.ts index 9046b31da0..887326d9b9 100644 --- a/src/useAsync.ts +++ b/src/useAsync.ts @@ -1,5 +1,6 @@ import { DependencyList, useEffect } from 'react'; -import useAsyncFn, { FnReturningPromise } from './useAsyncFn'; +import useAsyncFn from './useAsyncFn'; +import { FnReturningPromise } from './util'; export { AsyncState, AsyncFnReturn } from './useAsyncFn'; diff --git a/src/useAsyncFn.ts b/src/useAsyncFn.ts index cfdb17b608..ca5701ab65 100644 --- a/src/useAsyncFn.ts +++ b/src/useAsyncFn.ts @@ -1,5 +1,6 @@ import { DependencyList, useCallback, useState } from 'react'; import useMountedState from './useMountedState'; +import { FnReturningPromise, PromiseType } from './util'; export type AsyncState = | { @@ -18,16 +19,17 @@ export type AsyncState = value: T; }; -export type FnReturningPromise = (...args: any[]) => Promise; -export type AsyncFnReturn = [AsyncState>, T]; +type StateFromFnReturningPromise = AsyncState>>; -export default function useAsync( +export type AsyncFnReturn = [StateFromFnReturningPromise, T]; + +export default function useAsyncFn( fn: T, deps: DependencyList = [], - initialState: AsyncState> = { loading: false } + initialState: StateFromFnReturningPromise = { loading: false } ): AsyncFnReturn { const isMounted = useMountedState(); - const [state, set] = useState>>(initialState); + const [state, set] = useState>(initialState); const callback = useCallback((...args: Parameters): ReturnType => { set({ loading: true }); diff --git a/src/util.ts b/src/util.ts index 5e44fdcf75..37e60b7fe7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,3 +3,7 @@ export const isClient = typeof window === 'object'; export const on = (obj: any, ...args: any[]) => obj.addEventListener(...args); export const off = (obj: any, ...args: any[]) => obj.removeEventListener(...args); + +export type FnReturningPromise = (...args: any[]) => Promise; + +export type PromiseType

> = P extends Promise ? T : never; From 5cfddaf9b44137cc5b83fe8b032406d57a81edea Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Tue, 19 Nov 2019 19:59:04 -0800 Subject: [PATCH 0003/1144] add useLocalStorage tests --- package.json | 1 + tests/useLocalStorage.test.ts | 130 ++++++++++++++++++++++++++++++++++ yarn.lock | 5 ++ 3 files changed, 136 insertions(+) create mode 100644 tests/useLocalStorage.test.ts diff --git a/package.json b/package.json index f1f1862225..6640fcad9a 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "gh-pages": "2.1.1", "husky": "3.1.0", "jest": "24.9.0", + "jest-localstorage-mock": "^2.4.0", "keyboardjs": "2.5.1", "lint-staged": "9.4.3", "markdown-loader": "5.1.0", diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts new file mode 100644 index 0000000000..c4b2329529 --- /dev/null +++ b/tests/useLocalStorage.test.ts @@ -0,0 +1,130 @@ +import useLocalStorage from "../src/useLocalStorage"; +import "jest-localstorage-mock"; +import { renderHook, act } from "@testing-library/react-hooks"; + +describe(useLocalStorage, () => { + afterEach(() => localStorage.clear()); + it("retrieves an existing value from localStorage", () => { + localStorage.setItem("foo", "bar"); + const { result } = renderHook(() => useLocalStorage("foo")); + const [state] = result.current; + expect(state).toEqual("bar"); + }); + it("sets initial state", () => { + const { result } = renderHook(() => useLocalStorage("foo", "bar")); + const [state] = result.current; + expect(state).toEqual("bar"); + expect(localStorage.__STORE__["foo"]).toEqual("bar"); + }); + it("prefers existing value over initial state", () => { + localStorage.setItem("foo", "bar"); + const { result } = renderHook(() => useLocalStorage("foo", "baz")); + const [state] = result.current; + expect(state).toEqual("bar"); + }); + it("does not clobber existing localStorage with initialState", () => { + localStorage.setItem('foo', 'bar') + const { result } = renderHook(() => useLocalStorage('foo', 'buzz')); + result.current; // invoke current to make sure things are set + expect(localStorage.__STORE__['foo']).toEqual('bar'); + }) + it("correctly updates localStorage", () => { + const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + + const [, setFoo] = result.current; + act(() => setFoo("baz")); + rerender(); + + expect(localStorage.__STORE__["foo"]).toEqual("baz"); + }); + it("correctly and promptly returns a new value", () => { + const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + + const [, setFoo] = result.current; + act(() => setFoo("baz")); + rerender(); + + const [foo] = result.current; + expect(foo).toEqual("baz"); + }); + it("should not double-JSON-stringify stringy values", () => { + const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + + const [, setFoo] = result.current; + act(() => setFoo(JSON.stringify("baz"))); + rerender(); + + const [foo] = result.current; + expect(foo).toEqual("baz"); + }); + it("keeps multiple hooks accessing the same key in sync", () => { + localStorage.setItem("foo", "bar"); + const { result: r1, rerender: rerender1 } = renderHook(() => useLocalStorage("foo")); + const { result: r2, rerender: rerender2 } = renderHook(() => useLocalStorage("foo")); + + const [, setFoo] = r1.current; + act(() => setFoo("potato")); + rerender1(); + rerender2(); + + const [val1] = r1.current; + const [val2] = r2.current; + + expect(val1).toEqual(val2); + expect(val1).toEqual("potato"); + expect(val2).toEqual("potato"); + }); + it("parses out objects from localStorage", () => { + localStorage.setItem("foo", JSON.stringify({ ok: true })); + const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>("foo")); + const [foo] = result.current; + expect(foo.ok).toEqual(true); + }); + it("safely initializes objects to localStorage", () => { + const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>("foo", { ok: true })); + const [foo] = result.current; + expect(foo.ok).toEqual(true); + }); + it("safely sets objects to localStorage", () => { + const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>("foo", { ok: true })); + + const [, setFoo] = result.current; + act(() => setFoo({ ok: "bar" })); + rerender(); + + const [foo] = result.current; + expect(foo.ok).toEqual("bar"); + }); + it("safely returns objects from updates", () => { + const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>("foo", { ok: true })); + + const [, setFoo] = result.current; + act(() => setFoo({ ok: "bar" })); + rerender(); + + const [foo] = result.current; + expect(foo).toBeInstanceOf(Object); + expect(foo.ok).toEqual("bar"); + }); + it("sets localStorage from the function updater", () => { + const { result, rerender } = renderHook(() => + useLocalStorage<{ foo: string; fizz?: string }>("foo", { foo: "bar" }) + ); + + const [, setFoo] = result.current; + act(() => + setFoo(state => { + console.log(state); + return { ...state, fizz: "buzz" }; + }) + ); + rerender(); + + const [value] = result.current; + + console.log(value); + + expect(value.foo).toEqual("bar"); + expect(value.fizz).toEqual("buzz"); + }); +}); diff --git a/yarn.lock b/yarn.lock index fa8eaaa0fa..8b28813a4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7384,6 +7384,11 @@ jest-leak-detector@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-localstorage-mock@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.4.0.tgz#c6073810735dd3af74020ea6c3885ec1cc6d0d13" + integrity sha512-/mC1JxnMeuIlAaQBsDMilskC/x/BicsQ/BXQxEOw+5b1aGZkkOAqAF3nu8yq449CpzGtp5jJ5wCmDNxLgA2m6A== + jest-matcher-utils@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" From da4bfddb26c680e2f6ea1bf712d1118320834e01 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Tue, 19 Nov 2019 22:24:59 -0800 Subject: [PATCH 0004/1144] finish tests and hook --- src/useLocalStorage.ts | 69 +++++++++++++++++++++-------------- tests/useLocalStorage.test.ts | 45 ++++++++++++++++++----- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 051685be63..c803c152df 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -1,42 +1,55 @@ -import { useEffect, useState } from 'react'; import { isClient } from './util'; +import { useMemo, useCallback, useEffect, Dispatch, SetStateAction } from 'react'; -type Dispatch = (value: A) => void; -type SetStateAction = S | ((prevState: S) => S); - -const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, Dispatch>] => { - if (!isClient) { +const useLocalStorage = ( + key: string, + initialValue?: any, + raw?: boolean +): [any, Dispatch>] => { + if (!isClient || !localStorage) { return [initialValue as T, () => {}]; } - const [state, setState] = useState(() => { + let localStorageValue: string | null = null; + try { + localStorageValue = localStorage.getItem(key); + } catch { + // If user is in private mode or has storage restriction + // localStorage can throw. + localStorageValue = initialValue; + } + + const state = useMemo(() => { try { - const localStorageValue = localStorage.getItem(key); - if (typeof localStorageValue !== 'string') { - localStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue)); - return initialValue; - } else { - return raw ? localStorageValue : JSON.parse(localStorageValue || 'null'); - } + if (localStorageValue === null) return initialValue; // key hasn't been set yet + return raw ? localStorageValue : JSON.parse(localStorageValue); } catch { - // If user is in private mode or has storage restriction - // localStorage can throw. JSON.parse and JSON.stringify - // can throw, too. - return initialValue; + /* JSON.parse and JSON.stringify can throw. */ + return localStorageValue === null ? initialValue : localStorageValue; } - }); + }, [key, localStorageValue, initialValue]); + + const setState = useCallback( + (valOrFunc: any) => { + try { + let newState = typeof valOrFunc === 'function' ? valOrFunc(state) : valOrFunc; + newState = typeof newState === 'string' ? newState : JSON.stringify(newState); + localStorage.setItem(key, newState); + } catch { + /** + * If user is in private mode or has storage restriction + * localStorage can throw. Also JSON.stringify can throw. + */ + } + }, + [state, raw] + ); useEffect(() => { - try { - const serializedState = raw ? String(state) : JSON.stringify(state); - localStorage.setItem(key, serializedState); - } catch { - // If user is in private mode or has storage restriction - // localStorage can throw. Also JSON.stringify can throw. - } - }, [state]); + if (localStorageValue === null) setState(initialValue); + }, [localStorageValue, setState]); - return [state, setState]; + return [state as any, setState]; }; export default useLocalStorage; diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index c4b2329529..816cfa7e78 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -55,7 +55,8 @@ describe(useLocalStorage, () => { rerender(); const [foo] = result.current; - expect(foo).toEqual("baz"); + expect(foo).not.toMatch(/\\/i); // should not contain extra escapes + expect(foo).toBe('baz'); }); it("keeps multiple hooks accessing the same key in sync", () => { localStorage.setItem("foo", "bar"); @@ -112,19 +113,43 @@ describe(useLocalStorage, () => { ); const [, setFoo] = result.current; - act(() => - setFoo(state => { - console.log(state); - return { ...state, fizz: "buzz" }; - }) - ); + act(() => setFoo(state => ({ ...state, fizz: "buzz" }))); rerender(); const [value] = result.current; - - console.log(value); - expect(value.foo).toEqual("bar"); expect(value.fizz).toEqual("buzz"); }); + describe("raw setting", () => { + it('returns a string when localStorage is a stringified object', () => { + localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); + const { result } = renderHook(() => useLocalStorage('foo', null, true)); + const [foo] = result.current; + expect(typeof foo).toBe('string'); + }); + it('returns a string after an update', () => { + localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', null, true)); + + const [,setFoo] = result.current; + act(() => setFoo({ fizz: 'bang' })) + rerender(); + + const [foo] = result.current; + expect(typeof foo).toBe('string'); + expect(JSON.parse(foo)).toBeInstanceOf(Object); + expect(JSON.parse(foo).fizz).toEqual('bang'); + }); + it('still forces setState to a string', () => { + localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', null, true)); + + const [,setFoo] = result.current; + act(() => setFoo({ fizz: 'bang' })) + rerender(); + + const [value] = result.current; + expect(JSON.parse(value).fizz).toEqual('bang'); + }); + }); }); From 126c5091395cfe1ddb4b2bb9d4ff310db69dc532 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Tue, 19 Nov 2019 22:46:35 -0800 Subject: [PATCH 0005/1144] reject nullish keys --- src/useLocalStorage.ts | 3 +++ tests/useLocalStorage.test.ts | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index c803c152df..18567aeb31 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -9,6 +9,9 @@ const useLocalStorage = ( if (!isClient || !localStorage) { return [initialValue as T, () => {}]; } + if (!key && (key as any) !== 0) { + throw new Error('useLocalStorage key may not be nullish or undefined'); + } let localStorageValue: string | null = null; try { diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 816cfa7e78..2970882f1f 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -152,4 +152,13 @@ describe(useLocalStorage, () => { expect(JSON.parse(value).fizz).toEqual('bang'); }); }); + it('rejects nullish or undefined keys', () => { + const { result } = renderHook(() => useLocalStorage(null as any)); + try { + result.current; + fail('hook should have thrown'); + } catch (e) { + expect(String(e)).toMatch(/key may not be/i); + } + }); }); From ead224287509898fcac2d8e86bf1e6135df66bc9 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Tue, 19 Nov 2019 23:04:34 -0800 Subject: [PATCH 0006/1144] reintroduce types --- src/useLocalStorage.ts | 24 ++++++++++-------------- tests/useLocalStorage.test.ts | 30 +++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 18567aeb31..e3d2366660 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -1,11 +1,7 @@ import { isClient } from './util'; import { useMemo, useCallback, useEffect, Dispatch, SetStateAction } from 'react'; -const useLocalStorage = ( - key: string, - initialValue?: any, - raw?: boolean -): [any, Dispatch>] => { +const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, Dispatch>] => { if (!isClient || !localStorage) { return [initialValue as T, () => {}]; } @@ -19,12 +15,12 @@ const useLocalStorage = ( } catch { // If user is in private mode or has storage restriction // localStorage can throw. - localStorageValue = initialValue; } - const state = useMemo(() => { + const state: T = useMemo(() => { try { - if (localStorageValue === null) return initialValue; // key hasn't been set yet + /* If key hasn't been set yet */ + if (localStorageValue === null) return initialValue as T; return raw ? localStorageValue : JSON.parse(localStorageValue); } catch { /* JSON.parse and JSON.stringify can throw. */ @@ -32,10 +28,10 @@ const useLocalStorage = ( } }, [key, localStorageValue, initialValue]); - const setState = useCallback( - (valOrFunc: any) => { + const setState: Dispatch> = useCallback( + (valOrFunc: SetStateAction): void => { try { - let newState = typeof valOrFunc === 'function' ? valOrFunc(state) : valOrFunc; + let newState = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc; newState = typeof newState === 'string' ? newState : JSON.stringify(newState); localStorage.setItem(key, newState); } catch { @@ -48,11 +44,11 @@ const useLocalStorage = ( [state, raw] ); - useEffect(() => { - if (localStorageValue === null) setState(initialValue); + useEffect((): void => { + if (localStorageValue === null && initialValue) setState(initialValue); }, [localStorageValue, setState]); - return [state as any, setState]; + return [state, setState]; }; export default useLocalStorage; diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 2970882f1f..bb29a612e3 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -37,6 +37,18 @@ describe(useLocalStorage, () => { expect(localStorage.__STORE__["foo"]).toEqual("baz"); }); + it("returns and allow setting null", () => { + localStorage.setItem('foo', 'null'); + const { result, rerender } = renderHook(() => useLocalStorage('foo')); + + const [foo1, setFoo] = result.current; + act(() => setFoo(null)); + rerender(); + + const [foo2] = result.current; + expect(foo1).toEqual(null); + expect(foo2).toEqual(null); + }); it("correctly and promptly returns a new value", () => { const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); @@ -120,6 +132,15 @@ describe(useLocalStorage, () => { expect(value.foo).toEqual("bar"); expect(value.fizz).toEqual("buzz"); }); + it('rejects nullish or undefined keys', () => { + const { result } = renderHook(() => useLocalStorage(null as any)); + try { + result.current; + fail('hook should have thrown'); + } catch (e) { + expect(String(e)).toMatch(/key may not be/i); + } + }); describe("raw setting", () => { it('returns a string when localStorage is a stringified object', () => { localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); @@ -152,13 +173,4 @@ describe(useLocalStorage, () => { expect(JSON.parse(value).fizz).toEqual('bang'); }); }); - it('rejects nullish or undefined keys', () => { - const { result } = renderHook(() => useLocalStorage(null as any)); - try { - result.current; - fail('hook should have thrown'); - } catch (e) { - expect(String(e)).toMatch(/key may not be/i); - } - }); }); From affcbbd404a22d830ab832ddcfce9976d237ada0 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Tue, 19 Nov 2019 23:36:38 -0800 Subject: [PATCH 0007/1144] run prettier --- tests/useLocalStorage.test.ts | 122 +++++++++++++++++----------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index bb29a612e3..47c3d3dfe1 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -1,43 +1,43 @@ -import useLocalStorage from "../src/useLocalStorage"; -import "jest-localstorage-mock"; -import { renderHook, act } from "@testing-library/react-hooks"; +import useLocalStorage from '../src/useLocalStorage'; +import 'jest-localstorage-mock'; +import { renderHook, act } from '@testing-library/react-hooks'; describe(useLocalStorage, () => { afterEach(() => localStorage.clear()); - it("retrieves an existing value from localStorage", () => { - localStorage.setItem("foo", "bar"); - const { result } = renderHook(() => useLocalStorage("foo")); + it('retrieves an existing value from localStorage', () => { + localStorage.setItem('foo', 'bar'); + const { result } = renderHook(() => useLocalStorage('foo')); const [state] = result.current; - expect(state).toEqual("bar"); + expect(state).toEqual('bar'); }); - it("sets initial state", () => { - const { result } = renderHook(() => useLocalStorage("foo", "bar")); + it('sets initial state', () => { + const { result } = renderHook(() => useLocalStorage('foo', 'bar')); const [state] = result.current; - expect(state).toEqual("bar"); - expect(localStorage.__STORE__["foo"]).toEqual("bar"); + expect(state).toEqual('bar'); + expect(localStorage.__STORE__.foo).toEqual('bar'); }); - it("prefers existing value over initial state", () => { - localStorage.setItem("foo", "bar"); - const { result } = renderHook(() => useLocalStorage("foo", "baz")); + it('prefers existing value over initial state', () => { + localStorage.setItem('foo', 'bar'); + const { result } = renderHook(() => useLocalStorage('foo', 'baz')); const [state] = result.current; - expect(state).toEqual("bar"); + expect(state).toEqual('bar'); }); - it("does not clobber existing localStorage with initialState", () => { - localStorage.setItem('foo', 'bar') + it('does not clobber existing localStorage with initialState', () => { + localStorage.setItem('foo', 'bar'); const { result } = renderHook(() => useLocalStorage('foo', 'buzz')); result.current; // invoke current to make sure things are set - expect(localStorage.__STORE__['foo']).toEqual('bar'); - }) - it("correctly updates localStorage", () => { - const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + expect(localStorage.__STORE__.foo).toEqual('bar'); + }); + it('correctly updates localStorage', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); const [, setFoo] = result.current; - act(() => setFoo("baz")); + act(() => setFoo('baz')); rerender(); - expect(localStorage.__STORE__["foo"]).toEqual("baz"); + expect(localStorage.__STORE__.foo).toEqual('baz'); }); - it("returns and allow setting null", () => { + it('returns and allow setting null', () => { localStorage.setItem('foo', 'null'); const { result, rerender } = renderHook(() => useLocalStorage('foo')); @@ -49,34 +49,34 @@ describe(useLocalStorage, () => { expect(foo1).toEqual(null); expect(foo2).toEqual(null); }); - it("correctly and promptly returns a new value", () => { - const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + it('correctly and promptly returns a new value', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); const [, setFoo] = result.current; - act(() => setFoo("baz")); + act(() => setFoo('baz')); rerender(); const [foo] = result.current; - expect(foo).toEqual("baz"); + expect(foo).toEqual('baz'); }); - it("should not double-JSON-stringify stringy values", () => { - const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + it('should not double-JSON-stringify stringy values', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); const [, setFoo] = result.current; - act(() => setFoo(JSON.stringify("baz"))); + act(() => setFoo(JSON.stringify('baz'))); rerender(); const [foo] = result.current; expect(foo).not.toMatch(/\\/i); // should not contain extra escapes expect(foo).toBe('baz'); }); - it("keeps multiple hooks accessing the same key in sync", () => { - localStorage.setItem("foo", "bar"); - const { result: r1, rerender: rerender1 } = renderHook(() => useLocalStorage("foo")); - const { result: r2, rerender: rerender2 } = renderHook(() => useLocalStorage("foo")); + it('keeps multiple hooks accessing the same key in sync', () => { + localStorage.setItem('foo', 'bar'); + const { result: r1, rerender: rerender1 } = renderHook(() => useLocalStorage('foo')); + const { result: r2, rerender: rerender2 } = renderHook(() => useLocalStorage('foo')); const [, setFoo] = r1.current; - act(() => setFoo("potato")); + act(() => setFoo('potato')); rerender1(); rerender2(); @@ -84,53 +84,53 @@ describe(useLocalStorage, () => { const [val2] = r2.current; expect(val1).toEqual(val2); - expect(val1).toEqual("potato"); - expect(val2).toEqual("potato"); + expect(val1).toEqual('potato'); + expect(val2).toEqual('potato'); }); - it("parses out objects from localStorage", () => { - localStorage.setItem("foo", JSON.stringify({ ok: true })); - const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>("foo")); + it('parses out objects from localStorage', () => { + localStorage.setItem('foo', JSON.stringify({ ok: true })); + const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo')); const [foo] = result.current; expect(foo.ok).toEqual(true); }); - it("safely initializes objects to localStorage", () => { - const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>("foo", { ok: true })); + it('safely initializes objects to localStorage', () => { + const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo', { ok: true })); const [foo] = result.current; expect(foo.ok).toEqual(true); }); - it("safely sets objects to localStorage", () => { - const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>("foo", { ok: true })); + it('safely sets objects to localStorage', () => { + const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>('foo', { ok: true })); const [, setFoo] = result.current; - act(() => setFoo({ ok: "bar" })); + act(() => setFoo({ ok: 'bar' })); rerender(); const [foo] = result.current; - expect(foo.ok).toEqual("bar"); + expect(foo.ok).toEqual('bar'); }); - it("safely returns objects from updates", () => { - const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>("foo", { ok: true })); + it('safely returns objects from updates', () => { + const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>('foo', { ok: true })); const [, setFoo] = result.current; - act(() => setFoo({ ok: "bar" })); + act(() => setFoo({ ok: 'bar' })); rerender(); const [foo] = result.current; expect(foo).toBeInstanceOf(Object); - expect(foo.ok).toEqual("bar"); + expect(foo.ok).toEqual('bar'); }); - it("sets localStorage from the function updater", () => { + it('sets localStorage from the function updater', () => { const { result, rerender } = renderHook(() => - useLocalStorage<{ foo: string; fizz?: string }>("foo", { foo: "bar" }) + useLocalStorage<{ foo: string; fizz?: string }>('foo', { foo: 'bar' }) ); const [, setFoo] = result.current; - act(() => setFoo(state => ({ ...state, fizz: "buzz" }))); + act(() => setFoo(state => ({ ...state, fizz: 'buzz' }))); rerender(); const [value] = result.current; - expect(value.foo).toEqual("bar"); - expect(value.fizz).toEqual("buzz"); + expect(value.foo).toEqual('bar'); + expect(value.fizz).toEqual('buzz'); }); it('rejects nullish or undefined keys', () => { const { result } = renderHook(() => useLocalStorage(null as any)); @@ -141,7 +141,7 @@ describe(useLocalStorage, () => { expect(String(e)).toMatch(/key may not be/i); } }); - describe("raw setting", () => { + describe('raw setting', () => { it('returns a string when localStorage is a stringified object', () => { localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); const { result } = renderHook(() => useLocalStorage('foo', null, true)); @@ -152,8 +152,8 @@ describe(useLocalStorage, () => { localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); const { result, rerender } = renderHook(() => useLocalStorage('foo', null, true)); - const [,setFoo] = result.current; - act(() => setFoo({ fizz: 'bang' })) + const [, setFoo] = result.current; + act(() => setFoo({ fizz: 'bang' })); rerender(); const [foo] = result.current; @@ -165,8 +165,8 @@ describe(useLocalStorage, () => { localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); const { result, rerender } = renderHook(() => useLocalStorage('foo', null, true)); - const [,setFoo] = result.current; - act(() => setFoo({ fizz: 'bang' })) + const [, setFoo] = result.current; + act(() => setFoo({ fizz: 'bang' })); rerender(); const [value] = result.current; From 683e6581bf82bd5767bef035a4bebfc86cae5e20 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Tue, 19 Nov 2019 23:55:35 -0800 Subject: [PATCH 0008/1144] add some ignores to tests --- tests/useLocalStorage.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 47c3d3dfe1..81e9375880 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -153,12 +153,15 @@ describe(useLocalStorage, () => { const { result, rerender } = renderHook(() => useLocalStorage('foo', null, true)); const [, setFoo] = result.current; + // @ts-ignore act(() => setFoo({ fizz: 'bang' })); rerender(); const [foo] = result.current; expect(typeof foo).toBe('string'); + // @ts-ignore expect(JSON.parse(foo)).toBeInstanceOf(Object); + // @ts-ignore expect(JSON.parse(foo).fizz).toEqual('bang'); }); it('still forces setState to a string', () => { @@ -166,10 +169,12 @@ describe(useLocalStorage, () => { const { result, rerender } = renderHook(() => useLocalStorage('foo', null, true)); const [, setFoo] = result.current; + // @ts-ignore act(() => setFoo({ fizz: 'bang' })); rerender(); const [value] = result.current; + // @ts-ignore expect(JSON.parse(value).fizz).toEqual('bang'); }); }); From b702692d233f580060cad48da0d068b1ecd07232 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Wed, 20 Nov 2019 00:17:03 -0800 Subject: [PATCH 0009/1144] enforces rules of hooks --- src/useLocalStorage.ts | 2 +- tests/useLocalStorage.test.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index e3d2366660..9ab2c110ef 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -26,7 +26,7 @@ const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, D /* JSON.parse and JSON.stringify can throw. */ return localStorageValue === null ? initialValue : localStorageValue; } - }, [key, localStorageValue, initialValue]); + }, [key, localStorageValue]); const setState: Dispatch> = useCallback( (valOrFunc: SetStateAction): void => { diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 81e9375880..3a1872fbef 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -178,4 +178,34 @@ describe(useLocalStorage, () => { expect(JSON.parse(value).fizz).toEqual('bang'); }); }); + /* Enforces proper eslint react-hooks/rules-of-hooks usage */ + describe('eslint react-hooks/rules-of-hooks', () => { + it('memoizes an object between rerenders', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); + + result.current; // if localStorage isn't set then r1 and r2 will be different + rerender(); + const [r2] = result.current; + rerender(); + const [r3] = result.current; + expect(r2).toBe(r3); + }); + it('memoizes an object immediately if localStorage is already set', () => { + localStorage.setItem('foo', JSON.stringify({ ok: true })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); + + const [r1] = result.current; // if localStorage isn't set then r1 and r2 will be different + rerender(); + const [r2] = result.current; + expect(r1).toBe(r2); + }); + it('memoizes the setState function', () => { + localStorage.setItem('foo', JSON.stringify({ ok: true })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); + const [, s1] = result.current; + rerender(); + const [, s2] = result.current; + expect(s1).toBe(s2); + }); + }); }); From c8396443b4e4c5162c31452805702f0a4029fa54 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Mon, 25 Nov 2019 21:37:35 -0800 Subject: [PATCH 0010/1144] initialState switched to useEffectOnce --- src/useLocalStorage.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 9ab2c110ef..734c9e54ac 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -1,5 +1,6 @@ import { isClient } from './util'; -import { useMemo, useCallback, useEffect, Dispatch, SetStateAction } from 'react'; +import { useMemo, useCallback, Dispatch, SetStateAction } from 'react'; +import useEffectOnce from './useEffectOnce'; const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, Dispatch>] => { if (!isClient || !localStorage) { @@ -44,9 +45,10 @@ const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, D [state, raw] ); - useEffect((): void => { + /* If value hasn't been set yet (null not 'null') then initialize it. */ + useEffectOnce((): void => { if (localStorageValue === null && initialValue) setState(initialValue); - }, [localStorageValue, setState]); + }); return [state, setState]; }; From 41f9452722d6fb7d2628480d7ce657e4f08e441a Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Thu, 28 Nov 2019 17:27:21 +0100 Subject: [PATCH 0011/1144] fix: use latest set object in useSet "has" method --- src/useSet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useSet.ts b/src/useSet.ts index eb79a32be0..e7a32fde8b 100644 --- a/src/useSet.ts +++ b/src/useSet.ts @@ -17,7 +17,7 @@ const useSet = (initialSet = new Set()): [Set, Actions] => { remove: item => setSet(prevSet => new Set(Array.from(prevSet).filter(i => i !== item))), reset: () => setSet(initialSet), }), - [setSet] + [set, setSet] ); return [set, utils]; From 587de16ef5c85497d01e63247a578116d0605ff9 Mon Sep 17 00:00:00 2001 From: suyingtao Date: Fri, 13 Dec 2019 18:45:01 +0800 Subject: [PATCH 0012/1144] feat(useLocalStorage): add remove feature. (#229) --- docs/useLocalStorage.md | 3 ++- src/useLocalStorage.ts | 34 +++++++++++++++++++++++-------- stories/useLocalStorage.story.tsx | 7 +++++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/useLocalStorage.md b/docs/useLocalStorage.md index a414fdad56..d7040eef9b 100644 --- a/docs/useLocalStorage.md +++ b/docs/useLocalStorage.md @@ -9,13 +9,14 @@ React side-effect hook that manages a single `localStorage` key. import {useLocalStorage} from 'react-use'; const Demo = () => { - const [value, setValue] = useLocalStorage('my-key', 'foo'); + const [value, setValue, remove] = useLocalStorage('my-key', 'foo'); return (

); }; diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 051685be63..0415f06090 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -1,23 +1,31 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { isClient } from './util'; type Dispatch = (value: A) => void; type SetStateAction = S | ((prevState: S) => S); -const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, Dispatch>] => { +const noop = () => {}; + +const useLocalStorage = ( + key: string, + initialValue?: T, + raw?: boolean +): [T | null, Dispatch>, () => void] => { if (!isClient) { - return [initialValue as T, () => {}]; + return [initialValue as T, noop, noop]; } - const [state, setState] = useState(() => { + const [state, setState] = useState(() => { try { const localStorageValue = localStorage.getItem(key); + if (typeof initialValue === 'undefined' && typeof localStorageValue !== 'string') { + return null; + } if (typeof localStorageValue !== 'string') { localStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue)); return initialValue; - } else { - return raw ? localStorageValue : JSON.parse(localStorageValue || 'null'); } + return raw ? localStorageValue : JSON.parse(localStorageValue || 'null'); } catch { // If user is in private mode or has storage restriction // localStorage can throw. JSON.parse and JSON.stringify @@ -26,7 +34,18 @@ const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, D } }); + const remove = useCallback(() => { + try { + localStorage.removeItem(key); + setState(null); + } catch { + // If user is in private mode or has storage restriction + // localStorage can throw. + } + }, [key, setState]); + useEffect(() => { + if (state === null) return; try { const serializedState = raw ? String(state) : JSON.stringify(state); localStorage.setItem(key, serializedState); @@ -35,8 +54,7 @@ const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, D // localStorage can throw. Also JSON.stringify can throw. } }, [state]); - - return [state, setState]; + return [state, setState, remove]; }; export default useLocalStorage; diff --git a/stories/useLocalStorage.story.tsx b/stories/useLocalStorage.story.tsx index 31f8e87003..f246c9607f 100644 --- a/stories/useLocalStorage.story.tsx +++ b/stories/useLocalStorage.story.tsx @@ -5,12 +5,19 @@ import ShowDocs from './util/ShowDocs'; const Demo = () => { const [value, setValue] = useLocalStorage('hello-key', 'foo'); + const [removableValue, setRemovableValue, remove] = useLocalStorage('removeable-key'); return (
Value: {value}
+
+
+
Removable Value: {removableValue}
+ + +
); }; From cb0ed5f7584914d8d40ae1c3e83fd7d13707a818 Mon Sep 17 00:00:00 2001 From: MHA15 <30382711+MHA15@users.noreply.github.com> Date: Sun, 15 Dec 2019 12:44:51 +0330 Subject: [PATCH 0013/1144] change useUpdate hook to use `reducer` instead of `state + callback` if we using reducer instead of useState then we don't need a useCallback and it's performance will increase --- src/useUpdate.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/useUpdate.ts b/src/useUpdate.ts index d7f0d36853..1977b07ca9 100644 --- a/src/useUpdate.ts +++ b/src/useUpdate.ts @@ -1,11 +1,10 @@ import { useCallback, useState } from 'react'; -const incrementParameter = (num: number): number => ++num % 1_000_000; +const updateReducer = (num: number): number => (num + 1) % 1_000_000; const useUpdate = () => { - const [, setState] = useState(0); - // useCallback with empty deps as we only want to define updateCb once - return useCallback(() => setState(incrementParameter), []); + const [, update] = useReducer(updateReducer, 0); + return update; }; export default useUpdate; From 036d1c2f1f0f418e056bef199dbd5fb310ab289a Mon Sep 17 00:00:00 2001 From: MHA15 <30382711+MHA15@users.noreply.github.com> Date: Sun, 15 Dec 2019 18:16:55 +0330 Subject: [PATCH 0014/1144] fix import problems --- src/useUpdate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useUpdate.ts b/src/useUpdate.ts index 1977b07ca9..f4e08e2bb9 100644 --- a/src/useUpdate.ts +++ b/src/useUpdate.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useReducer } from 'react'; const updateReducer = (num: number): number => (num + 1) % 1_000_000; From baab9df948ad35c7e4bb76aec664ecc8f3a9fdd1 Mon Sep 17 00:00:00 2001 From: MHA15 <30382711+MHA15@users.noreply.github.com> Date: Sun, 15 Dec 2019 18:30:41 +0330 Subject: [PATCH 0015/1144] fix ts definition to get no argument for dispatch function --- src/useUpdate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useUpdate.ts b/src/useUpdate.ts index f4e08e2bb9..8a50dcb2f6 100644 --- a/src/useUpdate.ts +++ b/src/useUpdate.ts @@ -4,7 +4,7 @@ const updateReducer = (num: number): number => (num + 1) % 1_000_000; const useUpdate = () => { const [, update] = useReducer(updateReducer, 0); - return update; + return update as (() => void); }; export default useUpdate; From 1620e019fff94fb4a7a711fd3121ec02c7e99301 Mon Sep 17 00:00:00 2001 From: suyingtao Date: Mon, 16 Dec 2019 12:59:03 +0800 Subject: [PATCH 0016/1144] fix(useLocalStorage): using undefined for empty value instead of null --- src/useLocalStorage.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 0415f06090..7e6a3a64c1 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -5,21 +5,22 @@ type Dispatch
= (value: A) => void; type SetStateAction = S | ((prevState: S) => S); const noop = () => {}; +const isUndefined = (value?: any): boolean => typeof value === 'undefined'; const useLocalStorage = ( key: string, initialValue?: T, raw?: boolean -): [T | null, Dispatch>, () => void] => { +): [T | undefined, Dispatch>, () => void] => { if (!isClient) { return [initialValue as T, noop, noop]; } - const [state, setState] = useState(() => { + const [state, setState] = useState(() => { try { const localStorageValue = localStorage.getItem(key); - if (typeof initialValue === 'undefined' && typeof localStorageValue !== 'string') { - return null; + if (isUndefined(initialValue)) { + return undefined; } if (typeof localStorageValue !== 'string') { localStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue)); @@ -37,7 +38,7 @@ const useLocalStorage = ( const remove = useCallback(() => { try { localStorage.removeItem(key); - setState(null); + setState(undefined); } catch { // If user is in private mode or has storage restriction // localStorage can throw. @@ -45,7 +46,7 @@ const useLocalStorage = ( }, [key, setState]); useEffect(() => { - if (state === null) return; + if (isUndefined(state)) return; try { const serializedState = raw ? String(state) : JSON.stringify(state); localStorage.setItem(key, serializedState); From 733cf9bb072d16bc7a5432c5bb7ccd09a71c6301 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 31 Dec 2019 00:04:29 +0100 Subject: [PATCH 0017/1144] =?UTF-8?q?test:=20=F0=9F=92=8D=20fix=20useAsync?= =?UTF-8?q?=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/useAsync.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/useAsync.test.tsx b/tests/useAsync.test.tsx index 3993b6bc70..98fa253229 100644 --- a/tests/useAsync.test.tsx +++ b/tests/useAsync.test.tsx @@ -148,7 +148,7 @@ describe('useAsync', () => { hook = renderHook( ({ fn, counter }) => { const callback = useCallback(() => fn(counter), [counter]); - return useAsync(callback, [callback]); + return useAsync(callback, [callback]); }, { initialProps: { From 90ba9d000ff35039028cb66753114a6b0b452491 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Jan 2020 10:07:19 +0100 Subject: [PATCH 0018/1144] =?UTF-8?q?fix:=20=F0=9F=90=9B=20remove=20set=20?= =?UTF-8?q?dependencies=20in=20useSet=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useSet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useSet.ts b/src/useSet.ts index 680f8dceec..01a9ca2330 100644 --- a/src/useSet.ts +++ b/src/useSet.ts @@ -19,7 +19,7 @@ const useSet = (initialSet = new Set()): [Set, Actions] => { remove: item => setSet(prevSet => new Set(Array.from(prevSet).filter(i => i !== item))), reset: () => setSet(initialSet), }), - [set, setSet] + [setSet] ); const utils = { From e232bcc6fe9faae8a955d31a17aac5c01141dd80 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Jan 2020 10:08:40 +0100 Subject: [PATCH 0019/1144] Release 14.0.0-alpha.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b03ffeb2c..b64c6a8c27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "13.13.0", + "version": "14.0.0-alpha.0", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", From 091c9077ad52d72c720482df2290b862cbac3fa2 Mon Sep 17 00:00:00 2001 From: Alexey Bojhev <9medved@mail.ru> Date: Fri, 10 Jan 2020 03:57:56 +0300 Subject: [PATCH 0020/1144] upgrade useCustomCompareEffect.ts Added generic useCustomCompareEffect for dependencies inference in compare function --- src/useCustomCompareEffect.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/useCustomCompareEffect.ts b/src/useCustomCompareEffect.ts index 8f189188e4..28cd635044 100644 --- a/src/useCustomCompareEffect.ts +++ b/src/useCustomCompareEffect.ts @@ -2,9 +2,16 @@ import { DependencyList, EffectCallback, useEffect, useRef } from 'react'; const isPrimitive = (val: any) => val !== Object(val); -type DepsEqualFnType = (prevDeps: DependencyList, nextDeps: DependencyList) => boolean; - -const useCustomCompareEffect = (effect: EffectCallback, deps: DependencyList, depsEqual: DepsEqualFnType) => { +type DepsEqualFnType = ( + prevDeps: TDeps, + nextDeps: TDeps +) => boolean; + +const useCustomCompareEffect = ( + effect: EffectCallback, + deps: TDeps, + depsEqual: DepsEqualFnType +) => { if (process.env.NODE_ENV !== 'production') { if (!(deps instanceof Array) || !deps.length) { console.warn('`useCustomCompareEffect` should not be used with no dependencies. Use React.useEffect instead.'); @@ -21,7 +28,7 @@ const useCustomCompareEffect = (effect: EffectCallback, deps: DependencyList, de } } - const ref = useRef(undefined); + const ref = useRef(undefined); if (!ref.current || !depsEqual(deps, ref.current)) { ref.current = deps; From 4d8824064a0afbeba5a15597b007f8463fdbe027 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 17:11:35 +0100 Subject: [PATCH 0021/1144] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20impl?= =?UTF-8?q?ementation=20of=20useMeasure()=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: useMeasure() now defaults all values to -1, if they were not set and internal implementation heavily refactored. --- src/useMeasure.ts | 61 +++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/useMeasure.ts b/src/useMeasure.ts index 4622355f6a..29ac5ad8db 100644 --- a/src/useMeasure.ts +++ b/src/useMeasure.ts @@ -1,39 +1,48 @@ -import { useCallback, useState } from 'react'; +import { useState, useMemo } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; +import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; -export type ContentRect = Pick; +export type UseMeasureRect = Pick< + DOMRectReadOnly, + 'x' | 'y' | 'top' | 'left' | 'right' | 'bottom' | 'height' | 'width' +>; +export type UseMeasureRef = (element: HTMLElement) => void; +export type UseMeasureResult = [UseMeasureRef, UseMeasureRect]; -const useMeasure = (): [(instance: T) => void, ContentRect] => { - const [rect, set] = useState({ - x: 0, - y: 0, - width: 0, - height: 0, - top: 0, - left: 0, - bottom: 0, - right: 0, - }); +const defaultState: UseMeasureRect = { + x: -1, + y: -1, + width: -1, + height: -1, + top: -1, + left: -1, + bottom: -1, + right: -1, +}; + +const useMeasure = (): UseMeasureResult => { + const [element, ref] = useState(null); + const [rect, setRect] = useState(defaultState); - const [observer] = useState( + const observer = useMemo( () => new ResizeObserver(entries => { - const entry = entries[0]; - if (entry) { - set(entry.contentRect); + if (entries[0]) { + const { x, y, width, height, top, left, bottom, right } = entries[0].contentRect; + setRect({ x, y, width, height, top, left, bottom, right }); } - }) + }), + [] ); - const ref = useCallback( - node => { + useIsomorphicLayoutEffect(() => { + if (!element) return; + observer.observe(element); + return () => { observer.disconnect(); - if (node) { - observer.observe(node); - } - }, - [observer] - ); + }; + }, [element]); + return [ref, rect]; }; From 2bbc73a5f08e9a21bb3054527fc8ff9fd51cfd47 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 17:14:26 +0100 Subject: [PATCH 0022/1144] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20mock=20useMeas?= =?UTF-8?q?ure()=20hook=20on=20server=20and=20w/o=20ResizeObserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useMeasure.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/useMeasure.ts b/src/useMeasure.ts index 29ac5ad8db..37af79aae8 100644 --- a/src/useMeasure.ts +++ b/src/useMeasure.ts @@ -46,4 +46,6 @@ const useMeasure = (): UseMeasureResult => { return [ref, rect]; }; -export default useMeasure; +const useMeasureMock = () => [() => {}, defaultState]; + +export default !!(window as any).ResizeObserver ? useMeasure : useMeasureMock; From 4dfb25870f7d30ef14ecc6178e7efdce6b12f59d Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 17:17:21 +0100 Subject: [PATCH 0023/1144] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20mention?= =?UTF-8?q?=20ResizeObserver=20polyfill=20in=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/useMeasure.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/useMeasure.md b/docs/useMeasure.md index d8e87a0cad..306fec3b6a 100644 --- a/docs/useMeasure.md +++ b/docs/useMeasure.md @@ -25,6 +25,21 @@ const Demo = () => { }; ``` +This hook uses [`ResizeObserver` API][resize-observer], if you want to support +legacy browsers, consider installing [`resize-observer-polyfill`][resize-observer-polyfill] +before running your app. + +```js +if (!window.ResizeObserver) { + window.ResizeObserver = (await import('resize-observer-polyfill')).default; +} +``` + + ## Related hooks - [useSize](./useSize.md) + + +[resize-observer]: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver +[resize-observer-polyfill]: https://www.npmjs.com/package/resize-observer-polyfill From bf11131052c4a4ab2b9306486f0b171ac15057b0 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 17:48:07 +0100 Subject: [PATCH 0024/1144] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20remove=20resiz?= =?UTF-8?q?e-observer-polyfill=20from=20useMeasure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: resize-observer-polyfill package is not used with useMeasure() hook anymore. --- package.json | 3 + src/useMeasure.ts | 3 +- tests/_setup.js | 5 + tests/useMeasure.test.ts | 239 ++++++++++++++++++++++++++------------- 4 files changed, 171 insertions(+), 79 deletions(-) create mode 100644 tests/_setup.js diff --git a/package.json b/package.json index b64c6a8c27..0db04b2e5d 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,9 @@ "coverageDirectory": "coverage", "testMatch": [ "/tests/**/*.test.(ts|tsx)" + ], + "setupFiles": [ + "/tests/_setup.js" ] } } diff --git a/src/useMeasure.ts b/src/useMeasure.ts index 37af79aae8..57d5c6d3fb 100644 --- a/src/useMeasure.ts +++ b/src/useMeasure.ts @@ -1,5 +1,4 @@ import { useState, useMemo } from 'react'; -import ResizeObserver from 'resize-observer-polyfill'; import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; export type UseMeasureRect = Pick< @@ -26,7 +25,7 @@ const useMeasure = (): UseMeasureResult => { const observer = useMemo( () => - new ResizeObserver(entries => { + new (window as any).ResizeObserver(entries => { if (entries[0]) { const { x, y, width, height, top, left, bottom, right } = entries[0].contentRect; setRect({ x, y, width, height, top, left, bottom, right }); diff --git a/tests/_setup.js b/tests/_setup.js new file mode 100644 index 0000000000..b4816e4044 --- /dev/null +++ b/tests/_setup.js @@ -0,0 +1,5 @@ +window.ResizeObserver = class ResizeObserver { + constructor() {} + observe() {} + disconnect() {} +}; diff --git a/tests/useMeasure.test.ts b/tests/useMeasure.test.ts index 6d9da805b3..33c6f805d4 100644 --- a/tests/useMeasure.test.ts +++ b/tests/useMeasure.test.ts @@ -1,88 +1,79 @@ -import { act, renderHook } from '@testing-library/react-hooks'; -import useMeasure, { ContentRect } from '../src/useMeasure'; - -interface Entry { - target: HTMLElement; - contentRect: ContentRect; -} - -jest.mock('resize-observer-polyfill', () => { - return class ResizeObserver { - private cb: (entries: Entry[]) => void; - private map: WeakMap; - private targets: HTMLElement[]; - constructor(cb: () => void) { - this.cb = cb; - this.map = new WeakMap(); - this.targets = []; - } - public disconnect() { - this.targets.map(target => { - const originMethod = this.map.get(target); - target.setAttribute = originMethod; - this.map.delete(target); - }); - } - public observe(target: HTMLElement) { - const method = 'setAttribute'; - const originMethod = target[method]; - this.map.set(target, originMethod); - this.targets.push(target); - target[method] = (...args) => { - const [attrName, value] = args; - if (attrName === 'style') { - const rect: DOMRectReadOnly = { - x: 0, - y: 0, - top: 0, - left: 0, - right: 0, - bottom: 0, - width: 0, - height: 0, - } as DOMRectReadOnly; - value.split(';').map(kv => { - const [key, v] = kv.split(':'); - if (['top', 'bottom', 'left', 'right', 'width', 'height'].includes(key)) { - rect[key] = parseInt(v, 10); - } - }); - target.getBoundingClientRect = () => rect; - } - originMethod.apply(target, args); - this.fireCallback(); - }; - } - private fireCallback() { - if (this.cb) { - this.cb( - this.targets.map(target => { - return { - target, - contentRect: target.getBoundingClientRect() as ContentRect, - }; - }) - ); - } +import { renderHook, act } from '@testing-library/react-hooks'; +import useMeasure, { UseMeasureRef } from '../src/useMeasure'; + +it('by default, state defaults every value to -1', () => { + const { result } = renderHook(() => useMeasure()); + + act(() => { + const div = document.createElement('div'); + (result.current[0] as UseMeasureRef)(div); + }); + + expect(result.current[1]).toMatchObject({ + width: -1, + height: -1, + top: -1, + bottom: -1, + left: -1, + right: -1, + }); +}); + +it('synchronously sets up ResizeObserver listener', () => { + let listener: ((rect: any) => void) | undefined = undefined; + (window as any).ResizeObserver = class ResizeObserver { + constructor(ls) { + listener = ls; } + observe() {} + disconnect() {} }; + + const { result } = renderHook(() => useMeasure()); + + act(() => { + const div = document.createElement('div'); + (result.current[0] as UseMeasureRef)(div); + }); + + expect(typeof listener).toBe('function'); }); -it('reacts to changes in size of any of the observed elements', () => { +it('tracks rectangle of a DOM element', () => { + let listener: ((rect: any) => void) | undefined = undefined; + (window as any).ResizeObserver = class ResizeObserver { + constructor(ls) { + listener = ls; + } + observe() {} + disconnect() {} + }; + const { result } = renderHook(() => useMeasure()); - const div = document.createElement('div'); - result.current[0](div); - expect(result.current[1]).toMatchObject({ - width: 0, - height: 0, - top: 0, - bottom: 0, - left: 0, - right: 0, + + act(() => { + const div = document.createElement('div'); + (result.current[0] as UseMeasureRef)(div); + }); + + act(() => { + listener!([{ + contentRect: { + x: 1, + y: 2, + width: 200, + height: 200, + top: 100, + bottom: 0, + left: 100, + right: 0, + } + }]); }); - act(() => div.setAttribute('style', 'width:200px;height:200px;top:100;left:100')); expect(result.current[1]).toMatchObject({ + x: 1, + y: 2, width: 200, height: 200, top: 100, @@ -91,3 +82,97 @@ it('reacts to changes in size of any of the observed elements', () => { right: 0, }); }); + +it('tracks multiple updates', () => { + let listener: ((rect: any) => void) | undefined = undefined; + (window as any).ResizeObserver = class ResizeObserver { + constructor(ls) { + listener = ls; + } + observe() {} + disconnect() {} + }; + + const { result } = renderHook(() => useMeasure()); + + act(() => { + const div = document.createElement('div'); + (result.current[0] as UseMeasureRef)(div); + }); + + act(() => { + listener!([{ + contentRect: { + x: 1, + y: 1, + width: 1, + height: 1, + top: 1, + bottom: 1, + left: 1, + right: 1, + } + }]); + }); + + expect(result.current[1]).toMatchObject({ + x: 1, + y: 1, + width: 1, + height: 1, + top: 1, + bottom: 1, + left: 1, + right: 1, + }); + + act(() => { + listener!([{ + contentRect: { + x: 2, + y: 2, + width: 2, + height: 2, + top: 2, + bottom: 2, + left: 2, + right: 2, + } + }]); + }); + + expect(result.current[1]).toMatchObject({ + x: 2, + y: 2, + width: 2, + height: 2, + top: 2, + bottom: 2, + left: 2, + right: 2, + }); +}); + +it('calls .disconnect() on ResizeObserver when component unmounts', () => { + const disconnect = jest.fn(); + (window as any).ResizeObserver = class ResizeObserver { + constructor() {} + observe() {} + disconnect() { + disconnect(); + } + }; + + const { result, unmount } = renderHook(() => useMeasure()); + + act(() => { + const div = document.createElement('div'); + (result.current[0] as UseMeasureRef)(div); + }); + + expect(disconnect).toHaveBeenCalledTimes(0); + + unmount(); + + expect(disconnect).toHaveBeenCalledTimes(1); +}); From def75839d7cb950a9308731414df9ed315f3ef31 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 17:51:08 +0100 Subject: [PATCH 0025/1144] =?UTF-8?q?style:=20=F0=9F=92=84=20run=20Prettie?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/useMeasure.test.ts | 98 +++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/tests/useMeasure.test.ts b/tests/useMeasure.test.ts index 33c6f805d4..1f74e9e3ef 100644 --- a/tests/useMeasure.test.ts +++ b/tests/useMeasure.test.ts @@ -20,7 +20,7 @@ it('by default, state defaults every value to -1', () => { }); it('synchronously sets up ResizeObserver listener', () => { - let listener: ((rect: any) => void) | undefined = undefined; + let listener: ((rect: any) => void) | undefined; (window as any).ResizeObserver = class ResizeObserver { constructor(ls) { listener = ls; @@ -30,7 +30,7 @@ it('synchronously sets up ResizeObserver listener', () => { }; const { result } = renderHook(() => useMeasure()); - + act(() => { const div = document.createElement('div'); (result.current[0] as UseMeasureRef)(div); @@ -40,7 +40,7 @@ it('synchronously sets up ResizeObserver listener', () => { }); it('tracks rectangle of a DOM element', () => { - let listener: ((rect: any) => void) | undefined = undefined; + let listener: ((rect: any) => void) | undefined; (window as any).ResizeObserver = class ResizeObserver { constructor(ls) { listener = ls; @@ -50,25 +50,27 @@ it('tracks rectangle of a DOM element', () => { }; const { result } = renderHook(() => useMeasure()); - + act(() => { const div = document.createElement('div'); (result.current[0] as UseMeasureRef)(div); }); - + act(() => { - listener!([{ - contentRect: { - x: 1, - y: 2, - width: 200, - height: 200, - top: 100, - bottom: 0, - left: 100, - right: 0, - } - }]); + listener!([ + { + contentRect: { + x: 1, + y: 2, + width: 200, + height: 200, + top: 100, + bottom: 0, + left: 100, + right: 0, + }, + }, + ]); }); expect(result.current[1]).toMatchObject({ @@ -84,7 +86,7 @@ it('tracks rectangle of a DOM element', () => { }); it('tracks multiple updates', () => { - let listener: ((rect: any) => void) | undefined = undefined; + let listener: ((rect: any) => void) | undefined; (window as any).ResizeObserver = class ResizeObserver { constructor(ls) { listener = ls; @@ -94,25 +96,27 @@ it('tracks multiple updates', () => { }; const { result } = renderHook(() => useMeasure()); - + act(() => { const div = document.createElement('div'); (result.current[0] as UseMeasureRef)(div); }); - + act(() => { - listener!([{ - contentRect: { - x: 1, - y: 1, - width: 1, - height: 1, - top: 1, - bottom: 1, - left: 1, - right: 1, - } - }]); + listener!([ + { + contentRect: { + x: 1, + y: 1, + width: 1, + height: 1, + top: 1, + bottom: 1, + left: 1, + right: 1, + }, + }, + ]); }); expect(result.current[1]).toMatchObject({ @@ -125,20 +129,22 @@ it('tracks multiple updates', () => { left: 1, right: 1, }); - + act(() => { - listener!([{ - contentRect: { - x: 2, - y: 2, - width: 2, - height: 2, - top: 2, - bottom: 2, - left: 2, - right: 2, - } - }]); + listener!([ + { + contentRect: { + x: 2, + y: 2, + width: 2, + height: 2, + top: 2, + bottom: 2, + left: 2, + right: 2, + }, + }, + ]); }); expect(result.current[1]).toMatchObject({ @@ -164,7 +170,7 @@ it('calls .disconnect() on ResizeObserver when component unmounts', () => { }; const { result, unmount } = renderHook(() => useMeasure()); - + act(() => { const div = document.createElement('div'); (result.current[0] as UseMeasureRef)(div); From 58db2f989d5d4f75ac5e8ef54c25a9df8bb173a5 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 18:04:37 +0100 Subject: [PATCH 0026/1144] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20useScrat?= =?UTF-8?q?ch()=20sensor=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/useScratch.ts | 180 ++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 7 ++ 3 files changed, 188 insertions(+) create mode 100644 src/useScratch.ts diff --git a/package.json b/package.json index b64c6a8c27..ae9bb65c55 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "fast-shallow-equal": "^0.1.1", "nano-css": "^5.2.1", "react-fast-compare": "^2.0.4", + "react-universal-interface": "^0.6.0", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.0", "set-harmonic-interval": "^1.0.1", diff --git a/src/useScratch.ts b/src/useScratch.ts new file mode 100644 index 0000000000..7c9dd32a9d --- /dev/null +++ b/src/useScratch.ts @@ -0,0 +1,180 @@ +import { useState, useEffect, useRef, FC, cloneElement } from 'react'; +import { render } from 'react-universal-interface'; + +const noop = () => {}; + +export interface ScratchSensorParams { + disabled?: boolean; + onScratch?: (state: ScratchSensorState) => void; + onScratchStart?: (state: ScratchSensorState) => void; + onScratchEnd?: (state: ScratchSensorState) => void; +} + +export interface ScratchSensorState { + isScratching: boolean; + start?: number; + end?: number; + x?: number; + y?: number; + dx?: number; + dy?: number; + docX?: number; + docY?: number; + posX?: number; + posY?: number; + elH?: number; + elW?: number; + elX?: number; + elY?: number; +} + +const useScratch = ({ + disabled, + onScratch = noop, + onScratchStart = noop, + onScratchEnd = noop, +}: ScratchSensorParams = {}): [ScratchSensorState, (el: HTMLElement | null) => void] => { + const [state, setState] = useState({ isScratching: false }); + const refState = useRef(state); + const refScratching = useRef(false); + const refAnimationFrame = useRef(null); + const [el, setEl] = useState(null); + useEffect(() => { + if (disabled) return; + if (!el) return; + + const onMoveEvent = (docX, docY) => { + cancelAnimationFrame(refAnimationFrame.current); + refAnimationFrame.current = requestAnimationFrame(() => { + const { left, top } = el.getBoundingClientRect(); + const elX = left + window.scrollX; + const elY = top + window.scrollY; + const x = docX - elX; + const y = docY - elY; + setState(oldState => { + const newState = { + ...oldState, + dx: x - (oldState.x || 0), + dy: y - (oldState.y || 0), + end: Date.now(), + isScratching: true, + }; + refState.current = newState; + onScratch(newState); + return newState; + }); + }); + }; + + const onMouseMove = event => { + onMoveEvent(event.pageX, event.pageY); + }; + + const onTouchMove = event => { + onMoveEvent(event.changedTouches[0].pageX, event.changedTouches[0].pageY); + }; + + let onMouseUp; + let onTouchEnd; + + const stopScratching = () => { + if (!refScratching.current) return; + refScratching.current = false; + refState.current = { ...refState.current, isScratching: false }; + onScratchEnd(refState.current); + setState({ isScratching: false }); + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('touchmove', onTouchMove); + window.removeEventListener('mouseup', onMouseUp); + window.removeEventListener('touchend', onTouchEnd); + }; + + onMouseUp = stopScratching; + onTouchEnd = stopScratching; + + const startScratching = (docX, docY) => { + if (!refScratching.current) return; + const { left, top } = el.getBoundingClientRect(); + const elX = left + window.scrollX; + const elY = top + window.scrollY; + const x = docX - elX; + const y = docY - elY; + const time = Date.now(); + const newState = { + isScratching: true, + start: time, + end: time, + docX, + docY, + x, + y, + dx: 0, + dy: 0, + elH: el.offsetHeight, + elW: el.offsetWidth, + elX, + elY, + }; + refState.current = newState; + onScratchStart(newState); + setState(newState); + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('touchmove', onTouchMove); + window.addEventListener('mouseup', onMouseUp); + window.addEventListener('touchend', onTouchEnd); + }; + + const onMouseDown = event => { + refScratching.current = true; + startScratching(event.pageX, event.pageY); + }; + + const onTouchStart = event => { + refScratching.current = true; + startScratching(event.changedTouches[0].pageX, event.changedTouches[0].pageY); + }; + + el.addEventListener('mousedown', onMouseDown); + el.addEventListener('touchstart', onTouchStart); + + return () => { + el.removeEventListener('mousedown', onMouseDown); + el.removeEventListener('touchstart', onTouchStart); + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('touchmove', onTouchMove); + window.removeEventListener('mouseup', onMouseUp); + window.removeEventListener('touchend', onTouchEnd); + + if (refAnimationFrame.current) cancelAnimationFrame(refAnimationFrame.current); + refAnimationFrame.current = null; + + refScratching.current = false; + refState.current = { isScratching: false }; + setState(refState.current); + }; + }, [el, disabled]); + + return [state, setEl]; +}; + +export interface ScratchSensorProps extends ScratchSensorParams { + children: (state: ScratchSensorState, ref: (el: HTMLElement | null) => void) => React.ReactElement; +} + +export const ScratchSensor: FC = props => { + const { children, ...params } = props; + const [state, ref] = useScratch(params); + const element = render(props, state); + return cloneElement(element, { + ...element.props, + ref: el => { + if (element.props.ref) { + if (typeof element.props.ref === 'object') element.props.ref.current = el; + if (typeof element.props.ref === 'function') element.props.ref(el); + } + ref(el); + }, + }); +}; + +export default useScratch; diff --git a/yarn.lock b/yarn.lock index 72017e6c4c..81ea8a6ebd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11554,6 +11554,13 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-universal-interface@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.0.tgz#b65cbf7d71a2f3f7dd9705d8e4f06748539bd465" + integrity sha512-PzApKKWfd7gvDi1sU/D07jUqnLvFxYqvJi+GEtLvBO5tXJjKr2Sa8ETVHkMA7Jcvdwt7ttbPq7Sed1JpFdNqBQ== + dependencies: + tslib "^1.9.3" + react@16.12.0: version "16.12.0" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" From 4ca2d9b29e0c2badb758ad14ec855f038b611de6 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 18:17:56 +0100 Subject: [PATCH 0027/1144] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20useScrat?= =?UTF-8?q?ch()=20stories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 1 + src/useScratch.ts | 6 ++--- stories/useScratch.story.tsx | 46 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 stories/useScratch.story.tsx diff --git a/src/index.ts b/src/index.ts index b5f89826bd..e7800172ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,6 +67,7 @@ export { default as useRaf } from './useRaf'; export { default as useRafLoop } from './useRafLoop'; export { default as useRafState } from './useRafState'; export { default as useSearchParam } from './useSearchParam'; +export { default as useScratch } from './useScratch'; export { default as useScroll } from './useScroll'; export { default as useScrolling } from './useScrolling'; export { default as useSessionStorage } from './useSessionStorage'; diff --git a/src/useScratch.ts b/src/useScratch.ts index 7c9dd32a9d..35fafc65cd 100644 --- a/src/useScratch.ts +++ b/src/useScratch.ts @@ -33,7 +33,7 @@ const useScratch = ({ onScratch = noop, onScratchStart = noop, onScratchEnd = noop, -}: ScratchSensorParams = {}): [ScratchSensorState, (el: HTMLElement | null) => void] => { +}: ScratchSensorParams = {}): [(el: HTMLElement | null) => void, ScratchSensorState] => { const [state, setState] = useState({ isScratching: false }); const refState = useRef(state); const refScratching = useRef(false); @@ -154,7 +154,7 @@ const useScratch = ({ }; }, [el, disabled]); - return [state, setEl]; + return [setEl, state]; }; export interface ScratchSensorProps extends ScratchSensorParams { @@ -163,7 +163,7 @@ export interface ScratchSensorProps extends ScratchSensorParams { export const ScratchSensor: FC = props => { const { children, ...params } = props; - const [state, ref] = useScratch(params); + const [ref, state] = useScratch(params); const element = render(props, state); return cloneElement(element, { ...element.props, diff --git a/stories/useScratch.story.tsx b/stories/useScratch.story.tsx new file mode 100644 index 0000000000..49fe1edb2c --- /dev/null +++ b/stories/useScratch.story.tsx @@ -0,0 +1,46 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useScratch } from '../src'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const [ref, state] = useScratch(); + + const blockStyle: React.CSSProperties = { + position: 'relative', + width: 400, + height: 400, + border: '1px solid tomato', + }; + + const preStyle: React.CSSProperties = { + pointerEvents: 'none', + userSelect: 'none', + }; + + let { x = 0, y = 0, dx = 0, dy = 0 } = state; + if (dx < 0) [x, dx] = [x + dx, -dx]; + if (dy < 0) [y, dy] = [y + dy, -dy]; + + const rectangleStyle: React.CSSProperties = { + position: 'absolute', + left: x, + top: y, + width: dx, + height: dy, + border: '1px solid tomato', + pointerEvents: 'none', + userSelect: 'none', + }; + + return ( +
+
{JSON.stringify(state, null, 4)}
+ {state.isScratching &&
} +
+ ); +}; + +storiesOf('Sensors/useScratch', module) + // .add('Docs', () => ) + .add('Demo', () => ); From 9e651b8908c07044fde4d0e90c3dbc7e177f512d Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 18:21:43 +0100 Subject: [PATCH 0028/1144] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20add=20us?= =?UTF-8?q?eScratch=20to=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/useScratch.md | 75 ++++++++++++++++++++++++++++++++++++ stories/useScratch.story.tsx | 2 +- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/useScratch.md diff --git a/README.md b/README.md index e86e38c42e..e7b1b4b66b 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ - [`useNetwork`](./docs/useNetwork.md) — tracks state of user's internet connection. - [`useOrientation`](./docs/useOrientation.md) — tracks state of device's screen orientation. - [`usePageLeave`](./docs/usePageLeave.md) — triggers when mouse leaves page boundaries. + - [`useScratch`](./docs/useScratch.md) — tracks mouse click-and-scrub state. - [`useScroll`](./docs/useScroll.md) — tracks an HTML element's scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescroll--docs) - [`useScrolling`](./docs/useScrolling.md) — tracks whether HTML element is scrolling. - [`useSize`](./docs/useSize.md) — tracks an HTML element's size. diff --git a/docs/useScratch.md b/docs/useScratch.md new file mode 100644 index 0000000000..0ba6c31108 --- /dev/null +++ b/docs/useScratch.md @@ -0,0 +1,75 @@ +# `useScratch` + +React sensor hook that tracks state of mouse "scrubs" (or "scratches"). + +## Usage + +```jsx +import useScratch from 'react-use/lib/useScratch'; + +const Demo = () => { + const [ref, state] = useScratch(); + + const blockStyle: React.CSSProperties = { + position: 'relative', + width: 400, + height: 400, + border: '1px solid tomato', + }; + + const preStyle: React.CSSProperties = { + pointerEvents: 'none', + userSelect: 'none', + }; + + let { x = 0, y = 0, dx = 0, dy = 0 } = state; + if (dx < 0) [x, dx] = [x + dx, -dx]; + if (dy < 0) [y, dy] = [y + dy, -dy]; + + const rectangleStyle: React.CSSProperties = { + position: 'absolute', + left: x, + top: y, + width: dx, + height: dy, + border: '1px solid tomato', + pointerEvents: 'none', + userSelect: 'none', + }; + + return ( +
+
{JSON.stringify(state, null, 4)}
+ {state.isScratching &&
} +
+ ); +}; +``` + +## Reference + +```ts +const [ref, state] = useScratch(); +``` + +`state` is: + +```ts +export interface ScratchSensorState { + isScratching: boolean; + start?: number; + end?: number; + x?: number; + y?: number; + dx?: number; + dy?: number; + docX?: number; + docY?: number; + posX?: number; + posY?: number; + elH?: number; + elW?: number; + elX?: number; + elY?: number; +} +``` diff --git a/stories/useScratch.story.tsx b/stories/useScratch.story.tsx index 49fe1edb2c..43e9e31b3d 100644 --- a/stories/useScratch.story.tsx +++ b/stories/useScratch.story.tsx @@ -42,5 +42,5 @@ const Demo = () => { }; storiesOf('Sensors/useScratch', module) - // .add('Docs', () => ) + .add('Docs', () => ) .add('Demo', () => ); From 4bf9f9389f925964dc71fc816959bead745df4c3 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 12 Jan 2020 18:29:29 +0100 Subject: [PATCH 0029/1144] Release 14.0.0-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61ceed7a63..897df195d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "14.0.0-alpha.0", + "version": "14.0.0-alpha.1", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", From 61dcb1f1e23c2233a24b31f3ab986cbaa66b00b0 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Wed, 15 Jan 2020 20:31:10 -0600 Subject: [PATCH 0030/1144] test: add use copy to clipboard test --- tests/useCopyToClipboard.test.ts | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/useCopyToClipboard.test.ts diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts new file mode 100644 index 0000000000..a292fe8122 --- /dev/null +++ b/tests/useCopyToClipboard.test.ts @@ -0,0 +1,41 @@ +import writeText from 'copy-to-clipboard'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { useCopyToClipboard } from '../src'; + +jest.mock('copy-to-clipboard', () => jest.fn().mockImplementation((value: any) => typeof value === 'string')); + +describe('useCopyToClipboard', () => { + let hook; + + beforeEach(() => { + hook = renderHook(() => useCopyToClipboard()); + }); + + it('should be defined ', () => { + expect(useCopyToClipboard).toBeDefined(); + }); + + it('should pass a given value to copy to clipboard and update the state value if no error', () => { + const testValue = 'test'; + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(testValue)); + [state, copyToClipboard] = hook.result.current; + + expect(writeText).toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(true); + expect(state.error).not.toBeDefined(); + }); + + it('should set the corresponding noUserInteraction value if returned from copy to clipboard', () => { + const testValue = {}; // invalid value + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(testValue)); + [state, copyToClipboard] = hook.result.current; + + expect(writeText).toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(false); + expect(state.error).toBeDefined(); + }); +}); From b13667b55c2747a9285503f4bbf6c1c9b458dada Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 18:55:50 -0600 Subject: [PATCH 0031/1144] test: add more test cases --- tests/useCopyToClipboard.test.ts | 39 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index a292fe8122..84910b226c 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -2,7 +2,16 @@ import writeText from 'copy-to-clipboard'; import { renderHook, act } from '@testing-library/react-hooks'; import { useCopyToClipboard } from '../src'; -jest.mock('copy-to-clipboard', () => jest.fn().mockImplementation((value: any) => typeof value === 'string')); +const valueToRaiseMockException = 'fake input causing exception in copy to clipboard'; + +jest.mock('copy-to-clipboard', () => + jest.fn().mockImplementation(input => { + if (input === valueToRaiseMockException) { + throw new Error(input); + } + return true; + }) +); describe('useCopyToClipboard', () => { let hook; @@ -15,7 +24,7 @@ describe('useCopyToClipboard', () => { expect(useCopyToClipboard).toBeDefined(); }); - it('should pass a given value to copy to clipboard and update the state value if no error', () => { + it('should pass a given value to copy to clipboard and set state', () => { const testValue = 'test'; let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); @@ -27,15 +36,35 @@ describe('useCopyToClipboard', () => { expect(state.error).not.toBeDefined(); }); - it('should set the corresponding noUserInteraction value if returned from copy to clipboard', () => { + it('should only call writeText if passed a valid input and set state', () => { const testValue = {}; // invalid value let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); [state, copyToClipboard] = hook.result.current; - expect(writeText).toBeCalled(); + expect(writeText).not.toBeCalled(); expect(state.value).toBe(testValue); - expect(state.noUserInteraction).toBe(false); + expect(state.noUserInteraction).toBe(true); expect(state.error).toBeDefined(); }); + + it('should catch exception thrown by copy-to-clipboard and set state', () => { + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(valueToRaiseMockException)); + [state, copyToClipboard] = hook.result.current; + + expect(writeText).toBeCalledWith(valueToRaiseMockException); + expect(state.value).toBe(valueToRaiseMockException); + expect(state.noUserInteraction).not.toBeDefined(); + expect(state.error).toStrictEqual(new Error(valueToRaiseMockException)); + }); + it('should return initial state while unmounted', () => { + hook.unmount(); + const [state, copyToClipboard] = hook.result.current; + + act(() => copyToClipboard('value')); + expect(state.value).not.toBeDefined(); + expect(state.error).not.toBeDefined(); + expect(state.noUserInteraction).toBe(true); + }); }); From 86489d7f2bda3361b98c422b7a0993f28b6d8c79 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 18:58:14 -0600 Subject: [PATCH 0032/1144] refactor: update use copy to clipboard --- src/useCopyToClipboard.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/useCopyToClipboard.ts b/src/useCopyToClipboard.ts index fc3b296a43..60f55b118c 100644 --- a/src/useCopyToClipboard.ts +++ b/src/useCopyToClipboard.ts @@ -18,31 +18,33 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = }); const copyToClipboard = useCallback(value => { + if (!isMounted()) { + return; + } + let noUserInteraction; try { - if (process.env.NODE_ENV === 'development') { - if (typeof value !== 'string') { - console.error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`); - } - } - - const noUserInteraction = writeText(value); - - if (!isMounted()) { + if (typeof value !== 'string' && typeof value !== 'number') { + const error = new Error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`); + if (process.env.NODE_ENV === 'development') console.error(error); + setState({ + value, + error, + noUserInteraction: true, + }); return; } + const normalizedValue = value.toString(); + noUserInteraction = writeText(normalizedValue); setState({ - value, + value: normalizedValue, error: undefined, noUserInteraction, }); } catch (error) { - if (!isMounted()) { - return; - } setState({ - value: undefined, + value, error, - noUserInteraction: true, + noUserInteraction, }); } }, []); From 6d5624eee2d66e439623bb77169431ecf652f548 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:05:35 -0600 Subject: [PATCH 0033/1144] test: update case wording --- tests/useCopyToClipboard.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 84910b226c..39d467f765 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -36,7 +36,7 @@ describe('useCopyToClipboard', () => { expect(state.error).not.toBeDefined(); }); - it('should only call writeText if passed a valid input and set state', () => { + it('should not call writeText if passed an invalid input and set state', () => { const testValue = {}; // invalid value let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); @@ -58,6 +58,7 @@ describe('useCopyToClipboard', () => { expect(state.noUserInteraction).not.toBeDefined(); expect(state.error).toStrictEqual(new Error(valueToRaiseMockException)); }); + it('should return initial state while unmounted', () => { hook.unmount(); const [state, copyToClipboard] = hook.result.current; From d219ffa1ecd2912b32cdaeace8f8e1e0a871f068 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:17:50 -0600 Subject: [PATCH 0034/1144] test: add dev logging test case and reset mocks --- tests/useCopyToClipboard.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 39d467f765..2ea2ead3c0 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -12,6 +12,7 @@ jest.mock('copy-to-clipboard', () => return true; }) ); +jest.spyOn(global.console, 'error').mockImplementation(() => {}); describe('useCopyToClipboard', () => { let hook; @@ -20,6 +21,10 @@ describe('useCopyToClipboard', () => { hook = renderHook(() => useCopyToClipboard()); }); + afterAll(() => { + jest.restoreAllMocks(); + }); + it('should be defined ', () => { expect(useCopyToClipboard).toBeDefined(); }); @@ -68,4 +73,22 @@ describe('useCopyToClipboard', () => { expect(state.error).not.toBeDefined(); expect(state.noUserInteraction).toBe(true); }); + + it('should console error if in dev environment', () => { + const ORIGINAL_NODE_ENV = process.env.NODE_ENV; + const testValue = {}; // invalid value + + process.env.NODE_ENV = 'development'; + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(testValue)); + process.env.NODE_ENV = ORIGINAL_NODE_ENV; + + [state, copyToClipboard] = hook.result.current; + + expect(writeText).not.toBeCalled(); + expect(console.error).toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(true); + expect(state.error).toBeDefined(); + }); }); From 100b86898f19b1b4d9cf0145580aa43fca36db36 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:37:31 -0600 Subject: [PATCH 0035/1144] refactor: refuse empty strings --- src/useCopyToClipboard.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/useCopyToClipboard.ts b/src/useCopyToClipboard.ts index 60f55b118c..fb00c034e6 100644 --- a/src/useCopyToClipboard.ts +++ b/src/useCopyToClipboard.ts @@ -22,7 +22,9 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = return; } let noUserInteraction; + let normalizedValue; try { + // only strings and numbers casted to strings can be copied to clipboard if (typeof value !== 'string' && typeof value !== 'number') { const error = new Error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`); if (process.env.NODE_ENV === 'development') console.error(error); @@ -33,7 +35,18 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = }); return; } - const normalizedValue = value.toString(); + // empty strings are also considered invalid + else if (value === '') { + const error = new Error(`Cannot copy empty string to clipboard.`); + if (process.env.NODE_ENV === 'development') console.error(error); + setState({ + value, + error, + noUserInteraction: true, + }); + return; + } + normalizedValue = value.toString(); noUserInteraction = writeText(normalizedValue); setState({ value: normalizedValue, @@ -42,7 +55,7 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = }); } catch (error) { setState({ - value, + value: normalizedValue, error, noUserInteraction, }); From aaefdf85356450b88f9719903a4431535f6e4ace Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:41:43 -0600 Subject: [PATCH 0036/1144] test: update test to include empty string as invalid --- tests/useCopyToClipboard.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 2ea2ead3c0..4f73f8e099 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -42,7 +42,7 @@ describe('useCopyToClipboard', () => { }); it('should not call writeText if passed an invalid input and set state', () => { - const testValue = {}; // invalid value + let testValue = {}; // invalid value let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); [state, copyToClipboard] = hook.result.current; @@ -51,6 +51,15 @@ describe('useCopyToClipboard', () => { expect(state.value).toBe(testValue); expect(state.noUserInteraction).toBe(true); expect(state.error).toBeDefined(); + + testValue = ''; // emtpy string is also invalid + act(() => copyToClipboard(testValue)); + [state, copyToClipboard] = hook.result.current; + console.log(state); + expect(writeText).not.toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(true); + expect(state.error).toBeDefined(); }); it('should catch exception thrown by copy-to-clipboard and set state', () => { From f17c8a0f8e63bfddb8f13a094edbea1e3ee9680b Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:43:35 -0600 Subject: [PATCH 0037/1144] =?UTF-8?q?fix:=20remove=20console=20log=20?= =?UTF-8?q?=F0=9F=A4=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/useCopyToClipboard.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 4f73f8e099..fd8de8ebb4 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -55,7 +55,7 @@ describe('useCopyToClipboard', () => { testValue = ''; // emtpy string is also invalid act(() => copyToClipboard(testValue)); [state, copyToClipboard] = hook.result.current; - console.log(state); + expect(writeText).not.toBeCalled(); expect(state.value).toBe(testValue); expect(state.noUserInteraction).toBe(true); From 3cf4f44d6286560aecc3c8decc2e8570cf531005 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 17 Jan 2020 09:25:26 +0100 Subject: [PATCH 0038/1144] =?UTF-8?q?style:=20=F0=9F=92=84=20run=20Prettie?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useCustomCompareEffect.ts | 5 +---- src/useUpdate.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/useCustomCompareEffect.ts b/src/useCustomCompareEffect.ts index 28cd635044..b5c7314292 100644 --- a/src/useCustomCompareEffect.ts +++ b/src/useCustomCompareEffect.ts @@ -2,10 +2,7 @@ import { DependencyList, EffectCallback, useEffect, useRef } from 'react'; const isPrimitive = (val: any) => val !== Object(val); -type DepsEqualFnType = ( - prevDeps: TDeps, - nextDeps: TDeps -) => boolean; +type DepsEqualFnType = (prevDeps: TDeps, nextDeps: TDeps) => boolean; const useCustomCompareEffect = ( effect: EffectCallback, diff --git a/src/useUpdate.ts b/src/useUpdate.ts index 8a50dcb2f6..bcc4511ae3 100644 --- a/src/useUpdate.ts +++ b/src/useUpdate.ts @@ -4,7 +4,7 @@ const updateReducer = (num: number): number => (num + 1) % 1_000_000; const useUpdate = () => { const [, update] = useReducer(updateReducer, 0); - return update as (() => void); + return update as () => void; }; export default useUpdate; From 3a8ffef1a1bcb87b1a832ea161e06be3e7c13a70 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 17 Jan 2020 09:35:37 +0100 Subject: [PATCH 0039/1144] Release 14.0.0-alpha.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9082bf4e3..d589e44ce7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "14.0.0-alpha.1", + "version": "14.0.0-alpha.2", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", From 062e60663d5677fe0ad98c23c82f6c0086423da2 Mon Sep 17 00:00:00 2001 From: carlos Date: Thu, 30 Jan 2020 18:03:54 +0800 Subject: [PATCH 0040/1144] Resolve #934: useAsyncFn: keeping the previous state when start running the async function --- docs/useAsyncFn.md | 2 +- src/useAsyncFn.ts | 7 ++++++- tests/useAsyncFn.test.tsx | 27 +++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/useAsyncFn.md b/docs/useAsyncFn.md index cc4825fb5a..94cc243ece 100644 --- a/docs/useAsyncFn.md +++ b/docs/useAsyncFn.md @@ -32,5 +32,5 @@ const Demo = ({url}) => { ## Reference ```ts -useAsyncFn(fn, deps?: any[]); +useAsyncFn(fn, deps?: any[], initialState?: AsyncState); ``` diff --git a/src/useAsyncFn.ts b/src/useAsyncFn.ts index 0b0cd74875..31ad4f72e6 100644 --- a/src/useAsyncFn.ts +++ b/src/useAsyncFn.ts @@ -7,6 +7,11 @@ export type AsyncState = error?: undefined; value?: undefined; } + | { + loading: true; + error?: Error | undefined; + value?: T; + } | { loading: false; error: Error; @@ -35,7 +40,7 @@ export default function useAsyncFn( const callback = useCallback((...args: Args | []) => { const callId = ++lastCallId.current; - set({ loading: true }); + set(prevState => ({ ...prevState, loading: true })); return fn(...args).then( value => { diff --git a/tests/useAsyncFn.test.tsx b/tests/useAsyncFn.test.tsx index 63ca25b2df..4dcebfc326 100644 --- a/tests/useAsyncFn.test.tsx +++ b/tests/useAsyncFn.test.tsx @@ -126,4 +126,31 @@ describe('useAsyncFn', () => { await hook.waitForNextUpdate(); expect(hook.result.current[0]).toEqual({ loading: false, value: 2 }); }); + + it('should keeping value of initialState when loading', async () => { + const fetch = async () => 'new state'; + const initialState = { loading: false, value: 'init state' }; + + const hook = renderHook<{ fn: () => Promise }, [AsyncState, () => Promise]>( + ({ fn }) => useAsyncFn(fn, [fn], initialState), + { + initialProps: { fn: fetch }, + } + ); + + const [state, callback] = hook.result.current; + expect(state.loading).toBe(false); + expect(state.value).toBe('init state'); + + act(() => { + callback(); + }); + + expect(hook.result.current[0].loading).toBe(true); + expect(hook.result.current[0].value).toBe('init state'); + + await hook.waitForNextUpdate(); + expect(hook.result.current[0].loading).toBe(false); + expect(hook.result.current[0].value).toBe('new state'); + }); }); From becdfff19433b6e026645545d10681471dd6d573 Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Sun, 2 Feb 2020 21:21:34 -0800 Subject: [PATCH 0041/1144] add serializer options back to useLocalStorage --- src/useLocalStorage.ts | 17 ++++++++--------- tests/useLocalStorage.test.ts | 34 +++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index e92b530ac9..5378784d97 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -17,7 +17,6 @@ const useLocalStorage = ( initialValue?: T, options?: parserOptions ): [T, Dispatch>] => { - // TODO: !localStorage needed? What does isClient do? if (!isClient || !localStorage) { return [initialValue as T, () => {}]; } @@ -25,9 +24,8 @@ const useLocalStorage = ( throw new Error('useLocalStorage key may not be nullish or undefined'); } - // Use provided serializer/deserializer or the default ones - const serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify; - const deserializer = options ? (options.raw ? String : options.deserializer || null) : JSON.parse; + // @ts-ignore - These are allowed to be undefined + const { raw, deserializer, serializer } = options || {}; let localStorageValue: string | null = null; try { @@ -40,20 +38,21 @@ const useLocalStorage = ( const state: T = useMemo(() => { try { /* If key hasn't been set yet */ - console.log({ localStorageValue, initialValue, deserializer }); if (localStorageValue === null) return initialValue as T; - return deserializer ? deserializer(localStorageValue) : localStorageValue; + if (raw) return localStorageValue; + if (!raw && deserializer) return deserializer(localStorageValue); + return JSON.parse(localStorageValue); } catch { /* JSON.parse and JSON.stringify can throw. */ return localStorageValue === null ? initialValue : localStorageValue; } - }, [key, localStorageValue]); + }, [key, localStorageValue, raw, deserializer]); const setState: Dispatch> = useCallback( (valOrFunc: SetStateAction): void => { try { let newState = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc; - newState = typeof newState === 'string' ? newState : serializer(newState); + newState = typeof newState === 'string' ? newState : (serializer || JSON.stringify)(newState); localStorage.setItem(key, newState); } catch { /** @@ -62,7 +61,7 @@ const useLocalStorage = ( */ } }, - [state, serializer] + [state, raw, serializer] ); /* If value hasn't been set yet (null not 'null') then initialize it. */ diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index e27da12197..421e823d7f 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -58,6 +58,10 @@ describe(useLocalStorage, () => { expect(foo1).toEqual(null); expect(foo2).toEqual(null); }); + it('sets initialState if initialState is an object', () => { + renderHook(() => useLocalStorage('foo', { bar: true })); + expect(localStorage.__STORE__.foo).toEqual('{"bar":true}'); + }); it("correctly and promptly returns a new value", () => { const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); @@ -150,6 +154,21 @@ describe(useLocalStorage, () => { expect(String(e)).toMatch(/key may not be/i); } }); + it('should properly update the localStorageOnChange when component unmounts', () => { + const key = 'some_key'; + const updatedValue = { b: 'a' }; + const expectedValue = '{"b":"a"}'; + + const { result, unmount } = renderHook(() => useLocalStorage(key)); + + unmount(); + + act(() => { + result.current[1](updatedValue); + }); + + expect(localStorage.__STORE__[key]).toBe(expectedValue); + }); /* Enforces proper eslint react-hooks/rules-of-hooks usage */ describe("eslint react-hooks/rules-of-hooks", () => { it("memoizes an object between rerenders", () => { @@ -183,20 +202,15 @@ describe(useLocalStorage, () => { describe("Options: raw", () => { const STRINGIFIED_VALUE = '{"a":"b"}'; - const serializer = (_: string) => '321'; - const deserializer = (_: string) => '123'; - - const rawOption = { raw: true, serializer, deserializer }; - it("returns a string when localStorage is a stringified object", () => { localStorage.setItem("foo", JSON.stringify({ fizz: "buzz" })); - const { result } = renderHook(() => useLocalStorage("foo", null, rawOption)); + const { result } = renderHook(() => useLocalStorage("foo", null, { raw: true })); const [foo] = result.current; expect(typeof foo).toBe("string"); }); it("returns a string after an update", () => { localStorage.setItem("foo", JSON.stringify({ fizz: "buzz" })); - const { result, rerender } = renderHook(() => useLocalStorage("foo", null, rawOption)); + const { result, rerender } = renderHook(() => useLocalStorage("foo", null, { raw: true })); const [, setFoo] = result.current; // @ts-ignore @@ -210,10 +224,6 @@ describe(useLocalStorage, () => { // @ts-ignore expect(JSON.parse(foo).fizz).toEqual("bang"); }); - - - - it("still forces setState to a string", () => { localStorage.setItem("foo", JSON.stringify({ fizz: "buzz" })); const { result, rerender } = renderHook(() => useLocalStorage("foo", null, { raw: true })); @@ -236,7 +246,6 @@ describe(useLocalStorage, () => { expect(result.current[0]).toEqual(STRINGIFIED_VALUE); }); - it("should return initialValue if localStorage empty and set that to localStorage", () => { const key = "some_key"; @@ -249,7 +258,6 @@ describe(useLocalStorage, () => { describe("raw false and provided serializer/deserializer", () => { const serializer = (_: string) => "321"; const deserializer = (_: string) => "123"; - it("should return valid serialized value from existing localStorage key", () => { const key = "some_key"; localStorage.setItem(key, STRINGIFIED_VALUE); From 8d18ef949fe51a3abb6577c916d44512808f1a1e Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Sun, 2 Feb 2020 21:23:27 -0800 Subject: [PATCH 0042/1144] linting --- tests/useLocalStorage.test.ts | 200 +++++++++++++++++----------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 421e823d7f..ff1f623474 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -1,6 +1,6 @@ -import useLocalStorage from "../src/useLocalStorage"; -import "jest-localstorage-mock"; -import { renderHook, act } from "@testing-library/react-hooks"; +import useLocalStorage from '../src/useLocalStorage'; +import 'jest-localstorage-mock'; +import { renderHook, act } from '@testing-library/react-hooks'; describe(useLocalStorage, () => { afterEach(() => { @@ -8,47 +8,47 @@ describe(useLocalStorage, () => { jest.clearAllMocks(); }); - it("retrieves an existing value from localStorage", () => { - localStorage.setItem("foo", "bar"); - const { result } = renderHook(() => useLocalStorage("foo")); + it('retrieves an existing value from localStorage', () => { + localStorage.setItem('foo', 'bar'); + const { result } = renderHook(() => useLocalStorage('foo')); const [state] = result.current; - expect(state).toEqual("bar"); + expect(state).toEqual('bar'); }); - it("should return initialValue if localStorage empty and set that to localStorage", () => { - const { result } = renderHook(() => useLocalStorage("foo", "bar")); + it('should return initialValue if localStorage empty and set that to localStorage', () => { + const { result } = renderHook(() => useLocalStorage('foo', 'bar')); const [state] = result.current; - expect(state).toEqual("bar"); - expect(localStorage.__STORE__.foo).toEqual("bar"); + expect(state).toEqual('bar'); + expect(localStorage.__STORE__.foo).toEqual('bar'); }); - it("prefers existing value over initial state", () => { - localStorage.setItem("foo", "bar"); - const { result } = renderHook(() => useLocalStorage("foo", "baz")); + it('prefers existing value over initial state', () => { + localStorage.setItem('foo', 'bar'); + const { result } = renderHook(() => useLocalStorage('foo', 'baz')); const [state] = result.current; - expect(state).toEqual("bar"); + expect(state).toEqual('bar'); }); - it("does not clobber existing localStorage with initialState", () => { - localStorage.setItem("foo", "bar"); - const { result } = renderHook(() => useLocalStorage("foo", "buzz")); + it('does not clobber existing localStorage with initialState', () => { + localStorage.setItem('foo', 'bar'); + const { result } = renderHook(() => useLocalStorage('foo', 'buzz')); result.current; // invoke current to make sure things are set - expect(localStorage.__STORE__.foo).toEqual("bar"); + expect(localStorage.__STORE__.foo).toEqual('bar'); }); - it("correctly updates localStorage", () => { - const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + it('correctly updates localStorage', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); const [, setFoo] = result.current; - act(() => setFoo("baz")); + act(() => setFoo('baz')); rerender(); - expect(localStorage.__STORE__.foo).toEqual("baz"); + expect(localStorage.__STORE__.foo).toEqual('baz'); }); - it("should return undefined if no initialValue provided and localStorage empty", () => { - const { result } = renderHook(() => useLocalStorage("some_key")); + it('should return undefined if no initialValue provided and localStorage empty', () => { + const { result } = renderHook(() => useLocalStorage('some_key')); expect(result.current[0]).toBeUndefined(); }); - it("returns and allow setting null", () => { - localStorage.setItem("foo", "null"); - const { result, rerender } = renderHook(() => useLocalStorage("foo")); + it('returns and allow setting null', () => { + localStorage.setItem('foo', 'null'); + const { result, rerender } = renderHook(() => useLocalStorage('foo')); const [foo1, setFoo] = result.current; act(() => setFoo(null)); @@ -62,34 +62,34 @@ describe(useLocalStorage, () => { renderHook(() => useLocalStorage('foo', { bar: true })); expect(localStorage.__STORE__.foo).toEqual('{"bar":true}'); }); - it("correctly and promptly returns a new value", () => { - const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + it('correctly and promptly returns a new value', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); const [, setFoo] = result.current; - act(() => setFoo("baz")); + act(() => setFoo('baz')); rerender(); const [foo] = result.current; - expect(foo).toEqual("baz"); + expect(foo).toEqual('baz'); }); - it("should not double-JSON-stringify stringy values", () => { - const { result, rerender } = renderHook(() => useLocalStorage("foo", "bar")); + it('should not double-JSON-stringify stringy values', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); const [, setFoo] = result.current; - act(() => setFoo(JSON.stringify("baz"))); + act(() => setFoo(JSON.stringify('baz'))); rerender(); const [foo] = result.current; expect(foo).not.toMatch(/\\/i); // should not contain extra escapes - expect(foo).toBe("baz"); + expect(foo).toBe('baz'); }); - it("keeps multiple hooks accessing the same key in sync", () => { - localStorage.setItem("foo", "bar"); - const { result: r1, rerender: rerender1 } = renderHook(() => useLocalStorage("foo")); - const { result: r2, rerender: rerender2 } = renderHook(() => useLocalStorage("foo")); + it('keeps multiple hooks accessing the same key in sync', () => { + localStorage.setItem('foo', 'bar'); + const { result: r1, rerender: rerender1 } = renderHook(() => useLocalStorage('foo')); + const { result: r2, rerender: rerender2 } = renderHook(() => useLocalStorage('foo')); const [, setFoo] = r1.current; - act(() => setFoo("potato")); + act(() => setFoo('potato')); rerender1(); rerender2(); @@ -97,59 +97,59 @@ describe(useLocalStorage, () => { const [val2] = r2.current; expect(val1).toEqual(val2); - expect(val1).toEqual("potato"); - expect(val2).toEqual("potato"); + expect(val1).toEqual('potato'); + expect(val2).toEqual('potato'); }); - it("parses out objects from localStorage", () => { - localStorage.setItem("foo", JSON.stringify({ ok: true })); - const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>("foo")); + it('parses out objects from localStorage', () => { + localStorage.setItem('foo', JSON.stringify({ ok: true })); + const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo')); const [foo] = result.current; expect(foo.ok).toEqual(true); }); - it("safely initializes objects to localStorage", () => { - const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>("foo", { ok: true })); + it('safely initializes objects to localStorage', () => { + const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo', { ok: true })); const [foo] = result.current; expect(foo.ok).toEqual(true); }); - it("safely sets objects to localStorage", () => { - const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>("foo", { ok: true })); + it('safely sets objects to localStorage', () => { + const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>('foo', { ok: true })); const [, setFoo] = result.current; - act(() => setFoo({ ok: "bar" })); + act(() => setFoo({ ok: 'bar' })); rerender(); const [foo] = result.current; - expect(foo.ok).toEqual("bar"); + expect(foo.ok).toEqual('bar'); }); - it("safely returns objects from updates", () => { - const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>("foo", { ok: true })); + it('safely returns objects from updates', () => { + const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>('foo', { ok: true })); const [, setFoo] = result.current; - act(() => setFoo({ ok: "bar" })); + act(() => setFoo({ ok: 'bar' })); rerender(); const [foo] = result.current; expect(foo).toBeInstanceOf(Object); - expect(foo.ok).toEqual("bar"); + expect(foo.ok).toEqual('bar'); }); - it("sets localStorage from the function updater", () => { + it('sets localStorage from the function updater', () => { const { result, rerender } = renderHook(() => - useLocalStorage<{ foo: string; fizz?: string }>("foo", { foo: "bar" }) + useLocalStorage<{ foo: string; fizz?: string }>('foo', { foo: 'bar' }) ); const [, setFoo] = result.current; - act(() => setFoo(state => ({ ...state, fizz: "buzz" }))); + act(() => setFoo(state => ({ ...state, fizz: 'buzz' }))); rerender(); const [value] = result.current; - expect(value.foo).toEqual("bar"); - expect(value.fizz).toEqual("buzz"); + expect(value.foo).toEqual('bar'); + expect(value.fizz).toEqual('buzz'); }); - it("rejects nullish or undefined keys", () => { + it('rejects nullish or undefined keys', () => { const { result } = renderHook(() => useLocalStorage(null as any)); try { result.current; - fail("hook should have thrown"); + fail('hook should have thrown'); } catch (e) { expect(String(e)).toMatch(/key may not be/i); } @@ -170,9 +170,9 @@ describe(useLocalStorage, () => { expect(localStorage.__STORE__[key]).toBe(expectedValue); }); /* Enforces proper eslint react-hooks/rules-of-hooks usage */ - describe("eslint react-hooks/rules-of-hooks", () => { - it("memoizes an object between rerenders", () => { - const { result, rerender } = renderHook(() => useLocalStorage("foo", { ok: true })); + describe('eslint react-hooks/rules-of-hooks', () => { + it('memoizes an object between rerenders', () => { + const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); result.current; // if localStorage isn't set then r1 and r2 will be different rerender(); @@ -181,18 +181,18 @@ describe(useLocalStorage, () => { const [r3] = result.current; expect(r2).toBe(r3); }); - it("memoizes an object immediately if localStorage is already set", () => { - localStorage.setItem("foo", JSON.stringify({ ok: true })); - const { result, rerender } = renderHook(() => useLocalStorage("foo", { ok: true })); + it('memoizes an object immediately if localStorage is already set', () => { + localStorage.setItem('foo', JSON.stringify({ ok: true })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); const [r1] = result.current; // if localStorage isn't set then r1 and r2 will be different rerender(); const [r2] = result.current; expect(r1).toBe(r2); }); - it("memoizes the setState function", () => { - localStorage.setItem("foo", JSON.stringify({ ok: true })); - const { result, rerender } = renderHook(() => useLocalStorage("foo", { ok: true })); + it('memoizes the setState function', () => { + localStorage.setItem('foo', JSON.stringify({ ok: true })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); const [, s1] = result.current; rerender(); const [, s2] = result.current; @@ -200,54 +200,54 @@ describe(useLocalStorage, () => { }); }); - describe("Options: raw", () => { + describe('Options: raw', () => { const STRINGIFIED_VALUE = '{"a":"b"}'; - it("returns a string when localStorage is a stringified object", () => { - localStorage.setItem("foo", JSON.stringify({ fizz: "buzz" })); - const { result } = renderHook(() => useLocalStorage("foo", null, { raw: true })); + it('returns a string when localStorage is a stringified object', () => { + localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); + const { result } = renderHook(() => useLocalStorage('foo', null, { raw: true })); const [foo] = result.current; - expect(typeof foo).toBe("string"); + expect(typeof foo).toBe('string'); }); - it("returns a string after an update", () => { - localStorage.setItem("foo", JSON.stringify({ fizz: "buzz" })); - const { result, rerender } = renderHook(() => useLocalStorage("foo", null, { raw: true })); + it('returns a string after an update', () => { + localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', null, { raw: true })); const [, setFoo] = result.current; // @ts-ignore - act(() => setFoo({ fizz: "bang" })); + act(() => setFoo({ fizz: 'bang' })); rerender(); const [foo] = result.current; - expect(typeof foo).toBe("string"); + expect(typeof foo).toBe('string'); // @ts-ignore expect(JSON.parse(foo)).toBeInstanceOf(Object); // @ts-ignore - expect(JSON.parse(foo).fizz).toEqual("bang"); + expect(JSON.parse(foo).fizz).toEqual('bang'); }); - it("still forces setState to a string", () => { - localStorage.setItem("foo", JSON.stringify({ fizz: "buzz" })); - const { result, rerender } = renderHook(() => useLocalStorage("foo", null, { raw: true })); + it('still forces setState to a string', () => { + localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); + const { result, rerender } = renderHook(() => useLocalStorage('foo', null, { raw: true })); const [, setFoo] = result.current; // @ts-ignore - act(() => setFoo({ fizz: "bang" })); + act(() => setFoo({ fizz: 'bang' })); rerender(); const [value] = result.current; // @ts-ignore - expect(JSON.parse(value).fizz).toEqual("bang"); + expect(JSON.parse(value).fizz).toEqual('bang'); }); - describe("raw true", () => { - it("should set the value from existing localStorage key", () => { - const key = "some_key"; + describe('raw true', () => { + it('should set the value from existing localStorage key', () => { + const key = 'some_key'; localStorage.setItem(key, STRINGIFIED_VALUE); - const { result } = renderHook(() => useLocalStorage(key, "", { raw: true })); + const { result } = renderHook(() => useLocalStorage(key, '', { raw: true })); expect(result.current[0]).toEqual(STRINGIFIED_VALUE); }); - it("should return initialValue if localStorage empty and set that to localStorage", () => { - const key = "some_key"; + it('should return initialValue if localStorage empty and set that to localStorage', () => { + const key = 'some_key'; const { result } = renderHook(() => useLocalStorage(key, STRINGIFIED_VALUE, { raw: true })); @@ -255,18 +255,18 @@ describe(useLocalStorage, () => { expect(localStorage.__STORE__[key]).toBe(STRINGIFIED_VALUE); }); }); - describe("raw false and provided serializer/deserializer", () => { - const serializer = (_: string) => "321"; - const deserializer = (_: string) => "123"; - it("should return valid serialized value from existing localStorage key", () => { - const key = "some_key"; + describe('raw false and provided serializer/deserializer', () => { + const serializer = (_: string) => '321'; + const deserializer = (_: string) => '123'; + it('should return valid serialized value from existing localStorage key', () => { + const key = 'some_key'; localStorage.setItem(key, STRINGIFIED_VALUE); const { result } = renderHook(() => useLocalStorage(key, STRINGIFIED_VALUE, { raw: false, serializer, deserializer }) ); - expect(result.current[0]).toBe("123"); + expect(result.current[0]).toBe('123'); }); }); }); From e5f41678f8d62714d4aed45b4fc839693af5d98a Mon Sep 17 00:00:00 2001 From: Tyler Swavely Date: Sun, 2 Feb 2020 21:26:06 -0800 Subject: [PATCH 0043/1144] remove unused file --- tests/setupTests.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 tests/setupTests.ts diff --git a/tests/setupTests.ts b/tests/setupTests.ts deleted file mode 100644 index 1c787210fd..0000000000 --- a/tests/setupTests.ts +++ /dev/null @@ -1 +0,0 @@ -import 'jest-localstorage-mock'; From 68fb835ea64cf5587c99645a09c6de93ab1b71df Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 4 Feb 2020 00:38:56 +0100 Subject: [PATCH 0044/1144] =?UTF-8?q?fix:=20=F0=9F=90=9B=20better=20serial?= =?UTF-8?q?ization=20handling=20in=20useLocalStorage=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/useLocalStorage.ts | 78 +++++++++++----------- tests/useLocalStorage.test.ts | 121 ++++++++++++---------------------- 3 files changed, 83 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index 977a981819..d4d196dc23 100644 --- a/package.json +++ b/package.json @@ -163,8 +163,7 @@ "/tests/**/*.test.(ts|tsx)" ], "setupFiles": [ - "/tests/_setup.js", - "./tests/setupTests.ts" + "/tests/_setup.js" ] } } diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 14e6b85bca..d843417c5d 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -1,6 +1,5 @@ -import { useMemo, useCallback, Dispatch, SetStateAction } from 'react'; +import { useState, useCallback, Dispatch, SetStateAction } from 'react'; import { isClient } from './util'; -import useEffectOnce from './useEffectOnce'; type parserOptions = | { @@ -19,49 +18,57 @@ const useLocalStorage = ( initialValue?: T, options?: parserOptions ): [T | undefined, Dispatch>, () => void] => { - if (!isClient || !localStorage) { + if (!isClient) { return [initialValue as T, noop, noop]; } - if ((!key && (key as any) !== 0) || (key as any) === false) { - throw new Error('useLocalStorage key may not be nullish or undefined'); + if (!key) { + throw new Error('useLocalStorage key may not be falsy'); } - // Use provided serializer / deserializer or the default ones. - const serializer = options ? (options.raw ? String : options.serializer || JSON.stringify) : JSON.stringify; - const deserializer = options ? (options.raw ? value => value : options.deserializer || JSON.parse) : JSON.parse; + const deserializer = options ? (options.raw ? value => value : options.deserializer) : JSON.parse; - let localStorageValue: string | null = null; - try { - localStorageValue = localStorage.getItem(key); - } catch { - // If user is in private mode or has storage restriction - // localStorage can throw. - } - - const state: T = useMemo(() => { + const [state, setState] = useState(() => { try { - /* If key hasn't been set yet */ - if (localStorageValue === null) return initialValue as T; - return deserializer(localStorageValue); + const serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify; + + const localStorageValue = localStorage.getItem(key); + if (localStorageValue !== null) { + return deserializer(localStorageValue); + } else { + initialValue && localStorage.setItem(key, serializer(initialValue)); + return initialValue; + } } catch { - /* JSON.parse and JSON.stringify can throw. */ - return localStorageValue === null ? initialValue : localStorageValue; + // If user is in private mode or has storage restriction + // localStorage can throw. JSON.parse and JSON.stringify + // can throw, too. + return initialValue; } - }, [key, localStorageValue, deserializer]); + }); - const setState: Dispatch> = useCallback( - (valOrFunc: SetStateAction): void => { + const set: Dispatch> = useCallback( + valOrFunc => { try { - const value = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc; - localStorage.setItem(key, serializer(value)); + const newState = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc; + if (typeof newState === 'undefined') return; + let value: string; + + if (options) + if (options.raw) + if (typeof newState === 'string') value = newState; + else value = JSON.stringify(newState); + else if (options.serializer) value = options.serializer(newState); + else value = JSON.stringify(newState); + else value = JSON.stringify(newState); + + localStorage.setItem(key, value); + setState(deserializer(value)); } catch { - /** - * If user is in private mode or has storage restriction - * localStorage can throw. Also JSON.stringify can throw. - */ + // If user is in private mode or has storage restriction + // localStorage can throw. Also JSON.stringify can throw. } }, - [state, serializer] + [key, setState] ); const remove = useCallback(() => { @@ -74,12 +81,7 @@ const useLocalStorage = ( } }, [key, setState]); - /* If value hasn't been set yet (null not 'null') then initialize it. */ - useEffectOnce((): void => { - if (localStorageValue === null && initialValue) setState(initialValue); - }); - - return [state, setState, remove]; + return [state, set, remove]; }; export default useLocalStorage; diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index ff1f623474..696aeb8cfc 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -9,29 +9,33 @@ describe(useLocalStorage, () => { }); it('retrieves an existing value from localStorage', () => { - localStorage.setItem('foo', 'bar'); + localStorage.setItem('foo', '"bar"'); const { result } = renderHook(() => useLocalStorage('foo')); const [state] = result.current; expect(state).toEqual('bar'); }); + it('should return initialValue if localStorage empty and set that to localStorage', () => { const { result } = renderHook(() => useLocalStorage('foo', 'bar')); const [state] = result.current; expect(state).toEqual('bar'); - expect(localStorage.__STORE__.foo).toEqual('bar'); + expect(localStorage.__STORE__.foo).toEqual('"bar"'); }); + it('prefers existing value over initial state', () => { - localStorage.setItem('foo', 'bar'); + localStorage.setItem('foo', '"bar"'); const { result } = renderHook(() => useLocalStorage('foo', 'baz')); const [state] = result.current; expect(state).toEqual('bar'); }); + it('does not clobber existing localStorage with initialState', () => { - localStorage.setItem('foo', 'bar'); + localStorage.setItem('foo', '"bar"'); const { result } = renderHook(() => useLocalStorage('foo', 'buzz')); result.current; // invoke current to make sure things are set - expect(localStorage.__STORE__.foo).toEqual('bar'); + expect(localStorage.__STORE__.foo).toEqual('"bar"'); }); + it('correctly updates localStorage', () => { const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); @@ -39,13 +43,15 @@ describe(useLocalStorage, () => { act(() => setFoo('baz')); rerender(); - expect(localStorage.__STORE__.foo).toEqual('baz'); + expect(localStorage.__STORE__.foo).toEqual('"baz"'); }); + it('should return undefined if no initialValue provided and localStorage empty', () => { const { result } = renderHook(() => useLocalStorage('some_key')); expect(result.current[0]).toBeUndefined(); }); + it('returns and allow setting null', () => { localStorage.setItem('foo', 'null'); const { result, rerender } = renderHook(() => useLocalStorage('foo')); @@ -58,10 +64,12 @@ describe(useLocalStorage, () => { expect(foo1).toEqual(null); expect(foo2).toEqual(null); }); + it('sets initialState if initialState is an object', () => { renderHook(() => useLocalStorage('foo', { bar: true })); expect(localStorage.__STORE__.foo).toEqual('{"bar":true}'); }); + it('correctly and promptly returns a new value', () => { const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); @@ -72,17 +80,8 @@ describe(useLocalStorage, () => { const [foo] = result.current; expect(foo).toEqual('baz'); }); - it('should not double-JSON-stringify stringy values', () => { - const { result, rerender } = renderHook(() => useLocalStorage('foo', 'bar')); - const [, setFoo] = result.current; - act(() => setFoo(JSON.stringify('baz'))); - rerender(); - - const [foo] = result.current; - expect(foo).not.toMatch(/\\/i); // should not contain extra escapes - expect(foo).toBe('baz'); - }); + /* it('keeps multiple hooks accessing the same key in sync', () => { localStorage.setItem('foo', 'bar'); const { result: r1, rerender: rerender1 } = renderHook(() => useLocalStorage('foo')); @@ -100,17 +99,21 @@ describe(useLocalStorage, () => { expect(val1).toEqual('potato'); expect(val2).toEqual('potato'); }); + */ + it('parses out objects from localStorage', () => { localStorage.setItem('foo', JSON.stringify({ ok: true })); const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo')); const [foo] = result.current; - expect(foo.ok).toEqual(true); + expect(foo!.ok).toEqual(true); }); + it('safely initializes objects to localStorage', () => { const { result } = renderHook(() => useLocalStorage<{ ok: boolean }>('foo', { ok: true })); const [foo] = result.current; - expect(foo.ok).toEqual(true); + expect(foo!.ok).toEqual(true); }); + it('safely sets objects to localStorage', () => { const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>('foo', { ok: true })); @@ -119,8 +122,9 @@ describe(useLocalStorage, () => { rerender(); const [foo] = result.current; - expect(foo.ok).toEqual('bar'); + expect(foo!.ok).toEqual('bar'); }); + it('safely returns objects from updates', () => { const { result, rerender } = renderHook(() => useLocalStorage<{ ok: any }>('foo', { ok: true })); @@ -130,21 +134,23 @@ describe(useLocalStorage, () => { const [foo] = result.current; expect(foo).toBeInstanceOf(Object); - expect(foo.ok).toEqual('bar'); + expect(foo!.ok).toEqual('bar'); }); + it('sets localStorage from the function updater', () => { const { result, rerender } = renderHook(() => useLocalStorage<{ foo: string; fizz?: string }>('foo', { foo: 'bar' }) ); const [, setFoo] = result.current; - act(() => setFoo(state => ({ ...state, fizz: 'buzz' }))); + act(() => setFoo(state => ({ ...state!, fizz: 'buzz' }))); rerender(); const [value] = result.current; - expect(value.foo).toEqual('bar'); - expect(value.fizz).toEqual('buzz'); + expect(value!.foo).toEqual('bar'); + expect(value!.fizz).toEqual('buzz'); }); + it('rejects nullish or undefined keys', () => { const { result } = renderHook(() => useLocalStorage(null as any)); try { @@ -154,21 +160,7 @@ describe(useLocalStorage, () => { expect(String(e)).toMatch(/key may not be/i); } }); - it('should properly update the localStorageOnChange when component unmounts', () => { - const key = 'some_key'; - const updatedValue = { b: 'a' }; - const expectedValue = '{"b":"a"}'; - - const { result, unmount } = renderHook(() => useLocalStorage(key)); - - unmount(); - - act(() => { - result.current[1](updatedValue); - }); - expect(localStorage.__STORE__[key]).toBe(expectedValue); - }); /* Enforces proper eslint react-hooks/rules-of-hooks usage */ describe('eslint react-hooks/rules-of-hooks', () => { it('memoizes an object between rerenders', () => { @@ -181,6 +173,7 @@ describe(useLocalStorage, () => { const [r3] = result.current; expect(r2).toBe(r3); }); + it('memoizes an object immediately if localStorage is already set', () => { localStorage.setItem('foo', JSON.stringify({ ok: true })); const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); @@ -190,6 +183,7 @@ describe(useLocalStorage, () => { const [r2] = result.current; expect(r1).toBe(r2); }); + it('memoizes the setState function', () => { localStorage.setItem('foo', JSON.stringify({ ok: true })); const { result, rerender } = renderHook(() => useLocalStorage('foo', { ok: true })); @@ -201,73 +195,42 @@ describe(useLocalStorage, () => { }); describe('Options: raw', () => { - const STRINGIFIED_VALUE = '{"a":"b"}'; it('returns a string when localStorage is a stringified object', () => { localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); const { result } = renderHook(() => useLocalStorage('foo', null, { raw: true })); const [foo] = result.current; expect(typeof foo).toBe('string'); }); + it('returns a string after an update', () => { localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); const { result, rerender } = renderHook(() => useLocalStorage('foo', null, { raw: true })); const [, setFoo] = result.current; - // @ts-ignore - act(() => setFoo({ fizz: 'bang' })); + + act(() => setFoo({ fizz: 'bang' } as any)); rerender(); const [foo] = result.current; expect(typeof foo).toBe('string'); - // @ts-ignore - expect(JSON.parse(foo)).toBeInstanceOf(Object); - // @ts-ignore - expect(JSON.parse(foo).fizz).toEqual('bang'); + + expect(JSON.parse(foo!)).toBeInstanceOf(Object); + + // expect(JSON.parse(foo!).fizz).toEqual('bang'); }); + it('still forces setState to a string', () => { localStorage.setItem('foo', JSON.stringify({ fizz: 'buzz' })); const { result, rerender } = renderHook(() => useLocalStorage('foo', null, { raw: true })); const [, setFoo] = result.current; - // @ts-ignore - act(() => setFoo({ fizz: 'bang' })); + + act(() => setFoo({ fizz: 'bang' } as any)); rerender(); const [value] = result.current; - // @ts-ignore - expect(JSON.parse(value).fizz).toEqual('bang'); - }); - describe('raw true', () => { - it('should set the value from existing localStorage key', () => { - const key = 'some_key'; - localStorage.setItem(key, STRINGIFIED_VALUE); - - const { result } = renderHook(() => useLocalStorage(key, '', { raw: true })); - - expect(result.current[0]).toEqual(STRINGIFIED_VALUE); - }); - it('should return initialValue if localStorage empty and set that to localStorage', () => { - const key = 'some_key'; - const { result } = renderHook(() => useLocalStorage(key, STRINGIFIED_VALUE, { raw: true })); - - expect(result.current[0]).toBe(STRINGIFIED_VALUE); - expect(localStorage.__STORE__[key]).toBe(STRINGIFIED_VALUE); - }); - }); - describe('raw false and provided serializer/deserializer', () => { - const serializer = (_: string) => '321'; - const deserializer = (_: string) => '123'; - it('should return valid serialized value from existing localStorage key', () => { - const key = 'some_key'; - localStorage.setItem(key, STRINGIFIED_VALUE); - - const { result } = renderHook(() => - useLocalStorage(key, STRINGIFIED_VALUE, { raw: false, serializer, deserializer }) - ); - - expect(result.current[0]).toBe('123'); - }); + expect(JSON.parse(value!).fizz).toEqual('bang'); }); }); }); From d7117a988b6b772c6882393ef111d69ca4730cdc Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 4 Feb 2020 01:39:20 +0100 Subject: [PATCH 0045/1144] Release 14.0.0-alpha.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1879e0a218..169a2e40ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "14.0.0-alpha.2", + "version": "14.0.0-alpha.3", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", From 3c5f1fb1f135480e4b356c7d8e2f1a99ae56addf Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 15 Feb 2020 11:21:10 +0100 Subject: [PATCH 0046/1144] =?UTF-8?q?style:=20=F0=9F=92=84=20disable=20ESL?= =?UTF-8?q?int=20in=20couple=20of=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useScratch.ts | 1 + tests/useLocalStorage.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/useScratch.ts b/src/useScratch.ts index 35fafc65cd..4b71ccb601 100644 --- a/src/useScratch.ts +++ b/src/useScratch.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { useState, useEffect, useRef, FC, cloneElement } from 'react'; import { render } from 'react-universal-interface'; diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 696aeb8cfc..da01dc8698 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import useLocalStorage from '../src/useLocalStorage'; import 'jest-localstorage-mock'; import { renderHook, act } from '@testing-library/react-hooks'; From 9f608f59ce4965b435b6d63662aaeffa810281ae Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 15 Feb 2020 12:34:24 +0100 Subject: [PATCH 0047/1144] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20all=20c?= =?UTF-8?q?ontributors=20to=20GitHub=20heart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/FUNDING.yml | 99 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0b76ccb7b3..dbfac2729f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,100 @@ # These are supported funding model platforms -github: streamich +github: [ + "streamich", + "wardoost", + "xobotyi", + "Belco90", + "ankithkonda", + "ayush987goyal", + "NullVoxPopuli", + "lintuming", + "Granipouss", + "ythecombinator", + "james2406", + "jakapatb", + "MrHuangJser", + "zaguiini", + "ppeeou", + "liuyuchenzh", + "brickspert", + "artywhite", + "PetterIve", + "realdennis", + "lvl99", + "gelove", + "KusStar", + "xiaoxiangmoe", + "nmccready", + "mattleonowicz", + "kevinnorris", + "dubzzz", + "dependabot[bot]", + "ShizukuIchi", + "ManojBahuguna", + "Jivings", + "Dosant", + "zsh2401", + "xiaoboost", + "revskill10", + "mtinner", + "monkeywithacupcake", + "mitchheddles", + "maxzitron", + "macinjoke", + "jeetiss", + "ilyalesik", + "hijiangtao", + "f", + "elliottsj", + "droganov", + "denysdovhan", + "dabuside", + "benneq", + "azukaar", + "ariesjia", + "andrico1234", + "adesurirey", + "OBe95", + "FredyC", + "Cretezy", + "zyy7259", + "zslabs", + "vinitsood", + "uxitten", + "thevtm", + "tanem", + "suyingtao", + "srph", + "rkostrzewski", + "qianL93", + "o-alexandrov", + "nucleartux", + "natew", + "maxmalov", + "liaoyinglong", + "koenvanzuijlen", + "josmardias", + "jeemyeong", + "jazzqi", + "jakyle", + "jakeboone02", + "inker", + "glarivie", + "garrettmaring", + "dovidweisz", + "daniel-hauser", + "d-asensio", + "charlax", + "TylerR909", + "Rogdham", + "OctoD", + "MajorBreakfast", + "Jfelix61", + "Flydiverny", + "FlickerLogicalStack", + "DmacMcgreg", + "Dattaya", + "Andrey-Bazhanov", + "AlvaroBernalG" +] From 3343042eef6bbb39db3dd77ab4300c0f824ceddc Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 15 Feb 2020 14:52:14 +0100 Subject: [PATCH 0048/1144] Release 14.0.0-alpha.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ff0a99482..71eaea04ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "14.0.0-alpha.3", + "version": "14.0.0-alpha.4", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", From 2daf76990d0e1040f8c0f31e16e7c1eebd94c9bf Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 17 Feb 2020 22:42:07 +0100 Subject: [PATCH 0049/1144] =?UTF-8?q?fix:=20=F0=9F=90=9B=20make=20useMeasu?= =?UTF-8?q?re=20work=20on=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useMeasure.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/useMeasure.ts b/src/useMeasure.ts index 57d5c6d3fb..f8d42766d7 100644 --- a/src/useMeasure.ts +++ b/src/useMeasure.ts @@ -1,5 +1,6 @@ import { useState, useMemo } from 'react'; import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; +import { isClient } from './util'; export type UseMeasureRect = Pick< DOMRectReadOnly, @@ -47,4 +48,4 @@ const useMeasure = (): UseMeasureResult => { const useMeasureMock = () => [() => {}, defaultState]; -export default !!(window as any).ResizeObserver ? useMeasure : useMeasureMock; +export default (isClient && !!(window as any).ResizeObserver) ? useMeasure : useMeasureMock; From 3fa25177e9836daa626485502702e097f4ee2fde Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 17 Feb 2020 22:43:39 +0100 Subject: [PATCH 0050/1144] Release 14.0.0-alpha.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe90490a26..87d0f7bd50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "14.0.0-alpha.4", + "version": "14.0.0-alpha.5", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", From e4eec7bf63d2ebe031ef03fb66ac61c43a7f7e0e Mon Sep 17 00:00:00 2001 From: Mike Rogers Date: Thu, 5 Mar 2020 11:36:43 +0000 Subject: [PATCH 0051/1144] correcting spelling mistakes --- docs/Lifecycles.md | 2 +- docs/createReducer.md | 2 +- docs/useCopyToClipboard.md | 2 +- docs/useKeyPressEvent.md | 2 +- docs/useMultiStateValidator.md | 2 +- docs/useRendersCount.md | 2 +- docs/useStateList.md | 2 +- tests/useLocalStorage.test.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/Lifecycles.md b/docs/Lifecycles.md index 2a2a6fe5d5..d36e76366e 100644 --- a/docs/Lifecycles.md +++ b/docs/Lifecycles.md @@ -1,3 +1,3 @@ # Lifecycle -*"Lifecycle Hooks"* modify and extend built-in React hooks or immitate React Class component lifecycle patterns. +*"Lifecycle Hooks"* modify and extend built-in React hooks or imitate React Class component lifecycle patterns. diff --git a/docs/createReducer.md b/docs/createReducer.md index 2b53c6453b..b61131fd5d 100644 --- a/docs/createReducer.md +++ b/docs/createReducer.md @@ -1,6 +1,6 @@ # `createReducer` -Factory for reducer hooks with custom middleware with an identical API as [React's `useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer). Compatible with [Redux middlware](https://redux.js.org/advanced/middleware). +Factory for reducer hooks with custom middleware with an identical API as [React's `useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer). Compatible with [Redux middleware](https://redux.js.org/advanced/middleware). ## Usage diff --git a/docs/useCopyToClipboard.md b/docs/useCopyToClipboard.md index 825db3c992..a4822b34d3 100644 --- a/docs/useCopyToClipboard.md +++ b/docs/useCopyToClipboard.md @@ -28,5 +28,5 @@ const [{value, error, noUserInteraction}, copyToClipboard] = useCopyToClipboard( ``` - `value` — value that was copied to clipboard, undefined when nothing was copied. -- `error` — catched error when trying to copy to clipboard. +- `error` — caught error when trying to copy to clipboard. - `noUserInteraction` — boolean indicating if user interaction was required to copy the value to clipboard to expose full API from underlying [`copy-to-clipboard`](https://github.com/sudodoki/copy-to-clipboard) library. diff --git a/docs/useKeyPressEvent.md b/docs/useKeyPressEvent.md index 02b1be1f0e..3b29b0980a 100644 --- a/docs/useKeyPressEvent.md +++ b/docs/useKeyPressEvent.md @@ -1,6 +1,6 @@ # `useKeyPressEvent` -This hook fires `keydown` and `keyup` calllbacks, similar to how [`useKey`](./useKey.md) +This hook fires `keydown` and `keyup` callbacks, similar to how [`useKey`](./useKey.md) hook does, but it only triggers each callback once per press cycle. For example, if you press and hold a key, it will fire `keydown` callback only once. diff --git a/docs/useMultiStateValidator.md b/docs/useMultiStateValidator.md index 42de5b1e88..595fe3a62f 100644 --- a/docs/useMultiStateValidator.md +++ b/docs/useMultiStateValidator.md @@ -50,6 +50,6 @@ const [validity, revalidate] = useStateValidator( - **`validity`**_`: [boolean|null, ...any[]]`_ result of validity check. First element is strictly nullable boolean, but others can contain arbitrary data; - **`revalidate`**_`: ()=>void`_ runs validator once again - **`validator`**_`: (state, setValidity?)=>[boolean|null, ...any[]]`_ should return an array suitable for validity state described above; - - `states` - current states values as the've been passed to the hook; + - `states` - current states values as they've been passed to the hook; - `setValidity` - if defined hook will not trigger validity change automatically. Useful for async validators; - `initialValidity` - validity value which set when validity is nt calculated yet; diff --git a/docs/useRendersCount.md b/docs/useRendersCount.md index 05f6111125..134049939e 100644 --- a/docs/useRendersCount.md +++ b/docs/useRendersCount.md @@ -1,6 +1,6 @@ # `useRendersCount` -Tracks compontent's renders count including the first render. +Tracks component's renders count including the first render. ## Usage diff --git a/docs/useStateList.md b/docs/useStateList.md index 82158dcdb3..d6e03e0ab5 100644 --- a/docs/useStateList.md +++ b/docs/useStateList.md @@ -41,7 +41,7 @@ const Demo = () => { const { state, currentIndex, prev, next, setStateAt, setState } = useStateList(stateSet: T[] = []); ``` -If `stateSet` changed, became shorter than before and `currentIndex` left in shrinked gap - the last element of list will be taken as current. +If `stateSet` changed, became shorter than before and `currentIndex` left in shrunk gap - the last element of list will be taken as current. - **`state`**_`: T`_ — current state value; - **`currentIndex`**_`: number`_ — current state index; diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 95340c54e3..1c7df15cbe 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -34,7 +34,7 @@ it('should return initialValue if localStorage empty and set that to localStorag expect(localStorage.__STORE__[key]).toBe(`"${value}"`); }); -it('should return the value from localStorage if exists even if initialValue provied', () => { +it('should return the value from localStorage if exists even if initialValue provided', () => { const key = 'some_key'; localStorage.setItem(key, STRINGIFIED_VALUE); From ad29bea7b03f46aa697e6623bdf7a17347ace651 Mon Sep 17 00:00:00 2001 From: Sebastiaan ten Pas Date: Tue, 17 Mar 2020 16:10:14 +0000 Subject: [PATCH 0052/1144] fix: replace createFactory usages with createElement --- src/createReducerContext.ts | 4 ++-- src/createStateContext.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/createReducerContext.ts b/src/createReducerContext.ts index fab55fe7d2..35862e18dd 100644 --- a/src/createReducerContext.ts +++ b/src/createReducerContext.ts @@ -1,11 +1,11 @@ -import { createFactory, createContext, useContext, useReducer } from 'react'; +import { createElement, createContext, useContext, useReducer } from 'react'; const createReducerContext = >( reducer: R, defaultInitialState: React.ReducerState ) => { const context = createContext<[React.ReducerState, React.Dispatch>] | undefined>(undefined); - const providerFactory = createFactory(context.Provider); + const providerFactory = (props, children) => createElement(context.Provider, props, children); const ReducerProvider: React.FC<{ initialState?: React.ReducerState }> = ({ children, initialState }) => { const state = useReducer(reducer, initialState !== undefined ? initialState : defaultInitialState); diff --git a/src/createStateContext.ts b/src/createStateContext.ts index 57333b1a06..149d6f9d3f 100644 --- a/src/createStateContext.ts +++ b/src/createStateContext.ts @@ -1,8 +1,8 @@ -import { createFactory, createContext, useContext, useState } from 'react'; +import { createElement, createContext, useContext, useState } from 'react'; const createStateContext = (defaultInitialValue: T) => { const context = createContext<[T, React.Dispatch>] | undefined>(undefined); - const providerFactory = createFactory(context.Provider); + const providerFactory = (props, children) => createElement(context.Provider, props, children); const StateProvider: React.FC<{ initialValue?: T }> = ({ children, initialValue }) => { const state = useState(initialValue !== undefined ? initialValue : defaultInitialValue); From 542677b62a28d3a36bbd0ce41fd12b96e5605219 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 26 Mar 2020 18:45:33 +0000 Subject: [PATCH 0053/1144] chore(deps): update dependency eslint-plugin-react-hooks to v3 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0ee72b4b4b..6029c872ba 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "eslint-plugin-import": "2.20.1", "eslint-plugin-jsx-a11y": "6.2.3", "eslint-plugin-react": "7.19.0", - "eslint-plugin-react-hooks": "2.5.1", + "eslint-plugin-react-hooks": "3.0.0", "fork-ts-checker-webpack-plugin": "4.1.2", "gh-pages": "2.2.0", "husky": "4.2.3", diff --git a/yarn.lock b/yarn.lock index 2cf0bffd6d..bcc307179c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6897,10 +6897,10 @@ eslint-plugin-jsx-a11y@6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" -eslint-plugin-react-hooks@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz#4ef5930592588ce171abeb26f400c7fbcbc23cd0" - integrity sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g== +eslint-plugin-react-hooks@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz#9e80c71846eb68dd29c3b21d832728aa66e5bd35" + integrity sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw== eslint-plugin-react@7.19.0: version "7.19.0" From 1a5b3c9b03e33512cfcb2d463b2f3276c6e05f8c Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Mar 2020 04:25:58 +0000 Subject: [PATCH 0054/1144] chore(deps): update dependency eslint-plugin-import to v2.20.2 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index de2d599e36..50aae20626 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "eslint": "6.8.0", "eslint-config-react-app": "5.2.1", "eslint-plugin-flowtype": "4.7.0", - "eslint-plugin-import": "2.20.1", + "eslint-plugin-import": "2.20.2", "eslint-plugin-jsx-a11y": "6.2.3", "eslint-plugin-react": "7.19.0", "eslint-plugin-react-hooks": "2.5.1", diff --git a/yarn.lock b/yarn.lock index 86e764ef98..073b49b852 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6864,10 +6864,10 @@ eslint-plugin-flowtype@4.7.0: dependencies: lodash "^4.17.15" -eslint-plugin-import@2.20.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" - integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw== +eslint-plugin-import@2.20.2: + version "2.20.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== dependencies: array-includes "^3.0.3" array.prototype.flat "^1.2.1" From a788d63fa4281d8b42203e75e1455b2baa92b904 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Mar 2020 12:42:20 +0000 Subject: [PATCH 0055/1144] chore(deps): update dependency lint-staged to v10.0.10 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 50aae20626..50f51e96ec 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "jest": "25.2.3", "jest-localstorage-mock": "2.4.0", "keyboardjs": "2.5.1", - "lint-staged": "10.0.9", + "lint-staged": "10.0.10", "markdown-loader": "5.1.0", "prettier": "1.19.1", "raf-stub": "3.0.0", diff --git a/yarn.lock b/yarn.lock index 073b49b852..8f4fb9c3e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10095,10 +10095,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@10.0.9: - version "10.0.9" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.0.9.tgz#185aabb2432e9467c84add306c990f1c20da3cdb" - integrity sha512-NKJHYgRa8oI9c4Ic42ZtF2XA6Ps7lFbXwg3q0ZEP0r55Tw3YWykCW1RzW6vu+QIGqbsy7DxndvKu93Wtr5vPQw== +lint-staged@10.0.10: + version "10.0.10" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.0.10.tgz#d14d33ee02a31a31ad36cf9aa7973fc156c461b5" + integrity sha512-91vNy3eYStExElLWw1Idva5lghKpFaXh9AJqjcyrJXf7AYZrThi4EhQ+GpmiHdPmJJauKhZMMSzQR1bMB90MtA== dependencies: chalk "^3.0.0" commander "^4.0.1" From f20574b32fd755d49932561b99e18bcb9d9c1333 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Mar 2020 20:02:52 +0000 Subject: [PATCH 0056/1144] chore(deps): update dependency jest to v25.2.4 --- package.json | 2 +- yarn.lock | 282 +++++++++++++++++++++++++-------------------------- 2 files changed, 142 insertions(+), 142 deletions(-) diff --git a/package.json b/package.json index 50f51e96ec..98c591f754 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "fork-ts-checker-webpack-plugin": "4.1.2", "gh-pages": "2.2.0", "husky": "4.2.3", - "jest": "25.2.3", + "jest": "25.2.4", "jest-localstorage-mock": "2.4.0", "keyboardjs": "2.5.1", "lint-staged": "10.0.10", diff --git a/yarn.lock b/yarn.lock index 8f4fb9c3e1..a6ff868d44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2196,33 +2196,33 @@ jest-util "^25.2.3" slash "^3.0.0" -"@jest/core@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.2.3.tgz#2fd37ce0e6ad845e058dcd8245f2745490df1bc0" - integrity sha512-Ifz3aEkGvZhwijLMmWa7sloZVEMdxpzjFv3CKHv3eRYRShTN8no6DmyvvxaZBjLalOlRalJ7HDgc733J48tSuw== +"@jest/core@^25.2.4": + version "25.2.4" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.2.4.tgz#382ef80369d3311f1df79db1ee19e958ae95cdad" + integrity sha512-WcWYShl0Bqfcb32oXtjwbiR78D/djhMdJW+ulp4/bmHgeODcsieqUJfUH+kEv8M7VNV77E6jds5aA+WuGh1nmg== dependencies: "@jest/console" "^25.2.3" - "@jest/reporters" "^25.2.3" - "@jest/test-result" "^25.2.3" - "@jest/transform" "^25.2.3" + "@jest/reporters" "^25.2.4" + "@jest/test-result" "^25.2.4" + "@jest/transform" "^25.2.4" "@jest/types" "^25.2.3" ansi-escapes "^4.2.1" chalk "^3.0.0" exit "^0.1.2" graceful-fs "^4.2.3" jest-changed-files "^25.2.3" - jest-config "^25.2.3" + jest-config "^25.2.4" jest-haste-map "^25.2.3" - jest-message-util "^25.2.3" + jest-message-util "^25.2.4" jest-regex-util "^25.2.1" jest-resolve "^25.2.3" - jest-resolve-dependencies "^25.2.3" - jest-runner "^25.2.3" - jest-runtime "^25.2.3" - jest-snapshot "^25.2.3" + jest-resolve-dependencies "^25.2.4" + jest-runner "^25.2.4" + jest-runtime "^25.2.4" + jest-snapshot "^25.2.4" jest-util "^25.2.3" jest-validate "^25.2.3" - jest-watcher "^25.2.3" + jest-watcher "^25.2.4" micromatch "^4.0.2" p-each-series "^2.1.0" realpath-native "^2.0.0" @@ -2230,35 +2230,35 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.2.3.tgz#32b3f216355b03e9449b93b62584c18934a2cc4a" - integrity sha512-zRypAMQnNo8rD0rCbI9+5xf+Lu+uvunKZNBcIWjb3lTATSomKbgYO+GYewGDYn7pf+30XCNBc6SH1rnBUN1ioA== +"@jest/environment@^25.2.4": + version "25.2.4" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.2.4.tgz#74f4d8dd87b427434d0b822cde37bc0e78f3e28b" + integrity sha512-wA4xlhD19/gukkDpJ5HQsTle0pgnzI5qMFEjw267lpTDC8d9N7Ihqr5pI+l0p8Qn1SQhai+glSqxrGdzKy4jxw== dependencies: - "@jest/fake-timers" "^25.2.3" + "@jest/fake-timers" "^25.2.4" "@jest/types" "^25.2.3" jest-mock "^25.2.3" -"@jest/fake-timers@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.2.3.tgz#808a8a761be3baac719311f8bde1362bd1861e65" - integrity sha512-B6Qxm86fl613MV8egfvh1mRTMu23hMNdOUjzPhKl/4Nm5cceHz6nwLn0nP0sJXI/ue1vu71aLbtkgVBCgc2hYA== +"@jest/fake-timers@^25.2.4": + version "25.2.4" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.2.4.tgz#6821b6edde74fda2a42467ae92cc93095d4c9527" + integrity sha512-oC1TJiwfMcBttVN7Wz+VZnqEAgYTiEMu0QLOXpypR89nab0uCB31zm/QeBZddhSstn20qe3yqOXygp6OwvKT/Q== dependencies: "@jest/types" "^25.2.3" - jest-message-util "^25.2.3" + jest-message-util "^25.2.4" jest-mock "^25.2.3" jest-util "^25.2.3" lolex "^5.0.0" -"@jest/reporters@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.2.3.tgz#824e922ea56686d0243c910559c36adacdd2081c" - integrity sha512-S0Zca5e7tTfGgxGRvBh6hktNdOBzqc6HthPzYHPRFYVW81SyzCqHTaNZydtDIVehb9s6NlyYZpcF/I2vco+lNw== +"@jest/reporters@^25.2.4": + version "25.2.4" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.2.4.tgz#aa01c20aab217150d3a6080d5c98ce0bf34b17ed" + integrity sha512-VHbLxM03jCc+bTLOluW/IqHR2G0Cl0iATwIQbuZtIUast8IXO4fD0oy4jpVGpG5b20S6REA8U3BaQoCW/CeVNQ== dependencies: "@bcoe/v8-coverage" "^0.2.3" "@jest/console" "^25.2.3" - "@jest/test-result" "^25.2.3" - "@jest/transform" "^25.2.3" + "@jest/test-result" "^25.2.4" + "@jest/transform" "^25.2.4" "@jest/types" "^25.2.3" chalk "^3.0.0" collect-v8-coverage "^1.0.0" @@ -2290,31 +2290,31 @@ graceful-fs "^4.2.3" source-map "^0.6.0" -"@jest/test-result@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.2.3.tgz#db6028427514702c739dda66528dfbcc7fb8cdf4" - integrity sha512-cNYidqERTcT+xqZZ5FPSvji7Bd2YYq9M/VJCEUmgTVRFZRPOPSu65crEzQJ4czcDChEJ9ovzZ65r3UBlajnh3w== +"@jest/test-result@^25.2.4": + version "25.2.4" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.2.4.tgz#8fc9eac58e82eb2a82e4058e68c3814f98f59cf5" + integrity sha512-AI7eUy+q2lVhFnaibDFg68NGkrxVWZdD6KBr9Hm6EvN0oAe7GxpEwEavgPfNHQjU2mi6g+NsFn/6QPgTUwM1qg== dependencies: "@jest/console" "^25.2.3" - "@jest/transform" "^25.2.3" + "@jest/transform" "^25.2.4" "@jest/types" "^25.2.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.2.3.tgz#1400e0e994904844567e6e33c87062cbdf1f3e99" - integrity sha512-trHwV/wCrxWyZyNyNBUQExsaHyBVQxJwH3butpEcR+KBJPfaTUxtpXaxfs38IXXAhH68J4kPZgAaRRfkFTLunA== +"@jest/test-sequencer@^25.2.4": + version "25.2.4" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.2.4.tgz#28364aeddec140c696324114f63570f3de536c87" + integrity sha512-TEZm/Rkd6YgskdpTJdYLBtu6Gc11tfWPuSpatq0duH77ekjU8dpqX2zkPdY/ayuHxztV5LTJoV5BLtI9mZfXew== dependencies: - "@jest/test-result" "^25.2.3" + "@jest/test-result" "^25.2.4" jest-haste-map "^25.2.3" - jest-runner "^25.2.3" - jest-runtime "^25.2.3" + jest-runner "^25.2.4" + jest-runtime "^25.2.4" -"@jest/transform@^25.2.3": - version "25.2.3" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.2.3.tgz#f090bdd91f54b867631a76959f2b2fc566534ffe" - integrity sha512-w1nfAuYP4OAiEDprFkE/2iwU86jL/hK3j1ylMcYOA3my5VOHqX0oeBcBxS2fUKWse2V4izuO2jqes0yNTDMlzw== +"@jest/transform@^25.2.4": + version "25.2.4" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.2.4.tgz#34336f37f13f62f7d1f5b93d5d150ba9eb3e11b9" + integrity sha512-6eRigvb+G6bs4kW5j1/y8wu4nCrmVuIe0epPBbiWaYlwawJ8yi1EIyK3d/btDqmBpN5GpN4YhR6iPPnDmkYdTA== dependencies: "@babel/core" "^7.1.0" "@jest/types" "^25.2.3" @@ -4318,12 +4318,12 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.2.3.tgz#8f1c088b1954963e8a5384be2e219dae00d053f4" - integrity sha512-03JjvEwuDrEz/A45K8oggAv+Vqay0xcOdNTJxYFxiuZvB5vlHKo1iZg9Pi5vQTHhNCKpGLb7L/jvUUafyh9j7g== +babel-jest@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.2.4.tgz#b21b68d3af8f161c3e6e501e91f0dea8e652e344" + integrity sha512-+yDzlyJVWrqih9i2Cvjpt7COaN8vUwCsKGtxJLzg6I0xhxD54K8mvDUCliPKLufyzHh/c5C4MRj4Vk7VMjOjIg== dependencies: - "@jest/transform" "^25.2.3" + "@jest/transform" "^25.2.4" "@jest/types" "^25.2.3" "@types/babel__core" "^7.1.0" babel-plugin-istanbul "^6.0.0" @@ -7151,16 +7151,16 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/expect/-/expect-25.2.3.tgz#ee714f82bf33c43466fcef139ace0a57b3d0aa48" - integrity sha512-kil4jFRFAK2ySyCyXPqYrphc3EiiKKFd9BthrkKAyHcqr1B84xFTuj5kO8zL+eHRRjT2jQsOPExO0+1Q/fuUXg== +expect@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/expect/-/expect-25.2.4.tgz#b66e0777c861034ebc21730bb34e1839d5d46806" + integrity sha512-hfuPhPds4yOsZtIw4kwAg70r0hqGmpqekgA+VX7pf/3wZ6FY+xIOXZhNsPMMMsspYG/YIsbAiwqsdnD4Ht+bCA== dependencies: "@jest/types" "^25.2.3" ansi-styles "^4.0.0" jest-get-type "^25.2.1" jest-matcher-utils "^25.2.3" - jest-message-util "^25.2.3" + jest-message-util "^25.2.4" jest-regex-util "^25.2.1" express@^4.17.0: @@ -9250,41 +9250,41 @@ jest-changed-files@^25.2.3: execa "^3.2.0" throat "^5.0.0" -jest-cli@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.2.3.tgz#47e17240ce6d8ce824ca1a01468ea8824ec6b139" - integrity sha512-T7G0TOkFj0wr33ki5xoq3bxkKC+liwJfjV9SmYIKBozwh91W4YjL1o1dgVCUTB1+sKJa/DiAY0p+eXYE6v2RGw== +jest-cli@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.2.4.tgz#021c2383904696597abc060dcb133c82ebd8bfcc" + integrity sha512-zeY2pRDWKj2LZudIncvvguwLMEdcnJqc2jJbwza1beqi80qqLvkPF/BjbFkK2sIV3r+mfTJS+7ITrvK6pCdRjg== dependencies: - "@jest/core" "^25.2.3" - "@jest/test-result" "^25.2.3" + "@jest/core" "^25.2.4" + "@jest/test-result" "^25.2.4" "@jest/types" "^25.2.3" chalk "^3.0.0" exit "^0.1.2" import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^25.2.3" + jest-config "^25.2.4" jest-util "^25.2.3" jest-validate "^25.2.3" prompts "^2.0.1" realpath-native "^2.0.0" yargs "^15.3.1" -jest-config@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.2.3.tgz#c304e91e2ba3763c04b38eafc26d30e5c41b48e8" - integrity sha512-UpTNxN8DgmLLCXFizGuvwIw+ZAPB0T3jbKaFEkzJdGqhSsQrVrk1lxhZNamaVIpWirM2ptYmqwUzvoobGCEkiQ== +jest-config@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.2.4.tgz#f4f33238979f225683179c89d1e402893008975d" + integrity sha512-fxy3nIpwJqOUQJRVF/q+pNQb6dv5b9YufOeCbpPZJ/md1zXpiupbhfehpfODhnKOfqbzSiigtSLzlWWmbRxnqQ== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^25.2.3" + "@jest/test-sequencer" "^25.2.4" "@jest/types" "^25.2.3" - babel-jest "^25.2.3" + babel-jest "^25.2.4" chalk "^3.0.0" deepmerge "^4.2.2" glob "^7.1.1" - jest-environment-jsdom "^25.2.3" - jest-environment-node "^25.2.3" + jest-environment-jsdom "^25.2.4" + jest-environment-node "^25.2.4" jest-get-type "^25.2.1" - jest-jasmine2 "^25.2.3" + jest-jasmine2 "^25.2.4" jest-regex-util "^25.2.1" jest-resolve "^25.2.3" jest-util "^25.2.3" @@ -9331,25 +9331,25 @@ jest-each@^25.2.3: jest-util "^25.2.3" pretty-format "^25.2.3" -jest-environment-jsdom@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.2.3.tgz#f790f87c24878b219d1745f68343380c2d79ab01" - integrity sha512-TLg7nizxIYJafz6tOBAVSmO5Ekswf6Cf3Soseov+mgonXfdYi1I0OZlHlZMJb2fGyXem2ndYFCLrMkwcWPKAnQ== +jest-environment-jsdom@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.2.4.tgz#f2783541d0538b1bc43641703372cea6a2e83611" + integrity sha512-5dm+tNwrLmhELdjAwiQnVGf/U9iFMWdTL4/wyrMg2HU6RQnCiuxpWbIigLHUhuP1P2Ak0F4k3xhjrikboKyShA== dependencies: - "@jest/environment" "^25.2.3" - "@jest/fake-timers" "^25.2.3" + "@jest/environment" "^25.2.4" + "@jest/fake-timers" "^25.2.4" "@jest/types" "^25.2.3" jest-mock "^25.2.3" jest-util "^25.2.3" jsdom "^15.2.1" -jest-environment-node@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.2.3.tgz#e50a7e84bf7c7555216aa41aea1e48f53773318f" - integrity sha512-Tu/wlGXfoLtBR4Ym+isz58z3TJkMYX4VnFTkrsxaTGYAxNLN7ArCwL51Ki0WrMd89v+pbCLDj/hDjrb4a2sOrw== +jest-environment-node@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.2.4.tgz#dc211dfb0d8b66dfc1965a8f846e72e54ff0c430" + integrity sha512-Jkc5Y8goyXPrLRHnrUlqC7P4o5zn2m4zw6qWoRJ59kxV1f2a5wK+TTGhrhCwnhW/Ckpdl/pm+LufdvhJkvJbiw== dependencies: - "@jest/environment" "^25.2.3" - "@jest/fake-timers" "^25.2.3" + "@jest/environment" "^25.2.4" + "@jest/fake-timers" "^25.2.4" "@jest/types" "^25.2.3" jest-mock "^25.2.3" jest-util "^25.2.3" @@ -9384,25 +9384,25 @@ jest-haste-map@^25.2.3: optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.2.3.tgz#a824c5dbe383c63d243aab5e64cc85ab65f87598" - integrity sha512-x9PEGPFdnkSwJj1UG4QxG9JxFdyP8fuJ/UfKXd/eSpK8w9x7MP3VaQDuPQF0UQhCT0YeOITEPkQyqS+ptt0suA== +jest-jasmine2@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.2.4.tgz#5f77de83e1027f0c7588137055a80da773872374" + integrity sha512-juoKrmNmLwaheNbAg71SuUF9ovwUZCFNTpKVhvCXWk+SSeORcIUMptKdPCoLXV3D16htzhTSKmNxnxSk4SrTjA== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^25.2.3" + "@jest/environment" "^25.2.4" "@jest/source-map" "^25.2.1" - "@jest/test-result" "^25.2.3" + "@jest/test-result" "^25.2.4" "@jest/types" "^25.2.3" chalk "^3.0.0" co "^4.6.0" - expect "^25.2.3" + expect "^25.2.4" is-generator-fn "^2.0.0" jest-each "^25.2.3" jest-matcher-utils "^25.2.3" - jest-message-util "^25.2.3" - jest-runtime "^25.2.3" - jest-snapshot "^25.2.3" + jest-message-util "^25.2.4" + jest-runtime "^25.2.4" + jest-snapshot "^25.2.4" jest-util "^25.2.3" pretty-format "^25.2.3" throat "^5.0.0" @@ -9430,13 +9430,13 @@ jest-matcher-utils@^25.2.3: jest-get-type "^25.2.1" pretty-format "^25.2.3" -jest-message-util@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.2.3.tgz#a911c4e3af06df851cc6065d9a3119fd2a3aa240" - integrity sha512-DcyDmdO5LVIeS0ngRvd7rk701XL60dAakUeQJ1tQRby27fyLYXD+V0nqVaC194W7fIlohjVQOZPHmKXIjn+Byw== +jest-message-util@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.2.4.tgz#b1441b9c82f5c11fc661303cbf200a2f136a7762" + integrity sha512-9wWMH3Bf+GVTv0GcQLmH/FRr0x0toptKw9TA8U5YFLVXx7Tq9pvcNzTyJrcTJ+wLqNbMPPJlJNft4MnlcrtF5Q== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^25.2.3" + "@jest/test-result" "^25.2.4" "@jest/types" "^25.2.3" "@types/stack-utils" "^1.0.1" chalk "^3.0.0" @@ -9461,14 +9461,14 @@ jest-regex-util@^25.2.1: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.1.tgz#db64b0d15cd3642c93b7b9627801d7c518600584" integrity sha512-wroFVJw62LdqTdkL508ZLV82FrJJWVJMIuYG7q4Uunl1WAPTf4ftPKrqqfec4SvOIlvRZUdEX2TFpWR356YG/w== -jest-resolve-dependencies@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.2.3.tgz#cd4d9d068d5238dfbdfa45690f6e902b6413c2da" - integrity sha512-mcWlvjXLlNzgdE9EQxHuaeWICNxozanim87EfyvPwTY0ryWusFZbgF6F8u3E0syJ4FFSooEm0lQ6fgYcnPGAFw== +jest-resolve-dependencies@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.2.4.tgz#2d904400387d74a366dff54badb40a2b3210e733" + integrity sha512-qhUnK4PfNHzNdca7Ub1mbAqE0j5WNyMTwxBZZJjQlUrdqsiYho/QGK65FuBkZuSoYtKIIqriR9TpGrPEc3P5Gg== dependencies: "@jest/types" "^25.2.3" jest-regex-util "^25.2.1" - jest-snapshot "^25.2.3" + jest-snapshot "^25.2.4" jest-resolve@^25.2.3: version "25.2.3" @@ -9482,41 +9482,41 @@ jest-resolve@^25.2.3: realpath-native "^2.0.0" resolve "^1.15.1" -jest-runner@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.2.3.tgz#88fb448a46cf4ee9194a3e3cf0adbc122e195adb" - integrity sha512-E+u2Zm2TmtTOFEbKs5jllLiV2fwiX77cYc08RdyYZNe/s06wQT3P47aV6a8Rv61L7E2Is7OmozLd0KI/DITRpg== +jest-runner@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.2.4.tgz#d0daf7c56b4a83b6b675863d5cdcd502c960f9a1" + integrity sha512-5xaIfqqxck9Wg2CV4b9KmJtf/sWO7zWQx7O+34GCLGPzoPcVmB3mZtdrQI1/jS3Reqjru9ycLjgLHSf6XoxRqA== dependencies: "@jest/console" "^25.2.3" - "@jest/environment" "^25.2.3" - "@jest/test-result" "^25.2.3" + "@jest/environment" "^25.2.4" + "@jest/test-result" "^25.2.4" "@jest/types" "^25.2.3" chalk "^3.0.0" exit "^0.1.2" graceful-fs "^4.2.3" - jest-config "^25.2.3" + jest-config "^25.2.4" jest-docblock "^25.2.3" jest-haste-map "^25.2.3" - jest-jasmine2 "^25.2.3" + jest-jasmine2 "^25.2.4" jest-leak-detector "^25.2.3" - jest-message-util "^25.2.3" + jest-message-util "^25.2.4" jest-resolve "^25.2.3" - jest-runtime "^25.2.3" + jest-runtime "^25.2.4" jest-util "^25.2.3" jest-worker "^25.2.1" source-map-support "^0.5.6" throat "^5.0.0" -jest-runtime@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.2.3.tgz#1f0e9ba878a66538c3e9d58be97a6a12c877ed13" - integrity sha512-PZRFeUVF08N24v2G73SDF0b0VpLG7cRNOJ3ggj5TnArBVHkkrWzM3z7txB9OupWu7OO8bH/jFogk6sSjnHLFXQ== +jest-runtime@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.2.4.tgz#c66a421e115944426b377a7fd331f6c0902cfa56" + integrity sha512-6ehOUizgIghN+aV5YSrDzTZ+zJ9omgEjJbTHj3Jqes5D52XHfhzT7cSfdREwkNjRytrR7mNwZ7pRauoyNLyJ8Q== dependencies: "@jest/console" "^25.2.3" - "@jest/environment" "^25.2.3" + "@jest/environment" "^25.2.4" "@jest/source-map" "^25.2.1" - "@jest/test-result" "^25.2.3" - "@jest/transform" "^25.2.3" + "@jest/test-result" "^25.2.4" + "@jest/transform" "^25.2.4" "@jest/types" "^25.2.3" "@types/yargs" "^15.0.0" chalk "^3.0.0" @@ -9524,13 +9524,13 @@ jest-runtime@^25.2.3: exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.3" - jest-config "^25.2.3" + jest-config "^25.2.4" jest-haste-map "^25.2.3" - jest-message-util "^25.2.3" + jest-message-util "^25.2.4" jest-mock "^25.2.3" jest-regex-util "^25.2.1" jest-resolve "^25.2.3" - jest-snapshot "^25.2.3" + jest-snapshot "^25.2.4" jest-util "^25.2.3" jest-validate "^25.2.3" realpath-native "^2.0.0" @@ -9543,20 +9543,20 @@ jest-serializer@^25.2.1: resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.2.1.tgz#51727a5fc04256f461abe0fa024a022ba165877a" integrity sha512-fibDi7M5ffx6c/P66IkvR4FKkjG5ldePAK1WlbNoaU4GZmIAkS9Le/frAwRUFEX0KdnisSPWf+b1RC5jU7EYJQ== -jest-snapshot@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.2.3.tgz#2d432fcf9e7f1f7eb3e5012ffcce8035794b76ae" - integrity sha512-HlFVbE6vOZ541mtkwjuAe0rfx9EWhB+QXXneLNOP/s3LlHxGQtX7WFXY5OiH4CkAnCc6BpzLNYS9nfINNRb4Zg== +jest-snapshot@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.2.4.tgz#08d4517579c864df4280bcc948ceea34327a4ded" + integrity sha512-nIwpW7FZCq5p0AE3Oyqyb6jL0ENJixXzJ5/CD/XRuOqp3gS5OM3O/k+NnTrniCXxPFV4ry6s9HNfiPQBi0wcoA== dependencies: "@babel/types" "^7.0.0" "@jest/types" "^25.2.3" "@types/prettier" "^1.19.0" chalk "^3.0.0" - expect "^25.2.3" + expect "^25.2.4" jest-diff "^25.2.3" jest-get-type "^25.2.1" jest-matcher-utils "^25.2.3" - jest-message-util "^25.2.3" + jest-message-util "^25.2.4" jest-resolve "^25.2.3" make-dir "^3.0.0" natural-compare "^1.4.0" @@ -9585,12 +9585,12 @@ jest-validate@^25.2.3: leven "^3.1.0" pretty-format "^25.2.3" -jest-watcher@^25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.2.3.tgz#a494fe3ddb62da62b0e697abfea457de8f388f1f" - integrity sha512-F6ERbdvJk8nbaRon9lLQVl4kp+vToCCHmy+uWW5QQ8/8/g2jkrZKJQnlQINrYQp0ewg31Bztkhs4nxsZMx6wDg== +jest-watcher@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.2.4.tgz#dda85b914d470fa4145164a8f70bda4f208bafb6" + integrity sha512-p7g7s3zqcy69slVzQYcphyzkB2FBmJwMbv6k6KjI5mqd6KnUnQPfQVKuVj2l+34EeuxnbXqnrjtUFmxhcL87rg== dependencies: - "@jest/test-result" "^25.2.3" + "@jest/test-result" "^25.2.4" "@jest/types" "^25.2.3" ansi-escapes "^4.2.1" chalk "^3.0.0" @@ -9613,14 +9613,14 @@ jest-worker@^25.2.1: merge-stream "^2.0.0" supports-color "^7.0.0" -jest@25.2.3: - version "25.2.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-25.2.3.tgz#0cc9b35192f236fe1d5e76ed8eb3a54e7e0ee2e0" - integrity sha512-UbUmyGeZt0/sCIj/zsWOY0qFfQsx2qEFIZp0iEj8yVH6qASfR22fJOf12gFuSPsdSufam+llZBB0MdXWCg6EEQ== +jest@25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-25.2.4.tgz#d10941948a2b57eb7accc2e7ae78af4a0e11b40a" + integrity sha512-Lu4LXxf4+durzN/IFilcAoQSisOwgHIXgl9vffopePpSSwFqfj1Pj4y+k3nL8oTbnvjxgDIsEcepy6he4bWqnQ== dependencies: - "@jest/core" "^25.2.3" + "@jest/core" "^25.2.4" import-local "^3.0.2" - jest-cli "^25.2.3" + jest-cli "^25.2.4" js-cookie@^2.2.1: version "2.2.1" From b5642410397bb886ab352656d79694d1e5ef95eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=9A=E8=87=B4=E8=BF=9C?= Date: Mon, 30 Mar 2020 13:37:11 +0800 Subject: [PATCH 0057/1144] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1664243297..08260ad5dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,8 +31,8 @@ This library is a collection of React hooks so a proposal for a new hook will ne ### Creating a new hook -1. Create `src/useYourHookName.ts` and `src/__stories__/useYourHookName.story.tsx`, run `yarn start` to start the storybook development server and start coding your hook -1. Create `src/__tests__/useYourHookName.test.ts`, run `yarn test:watch` to start the test runner in watch mode and start writing tests for your hook +1. Create `src/useYourHookName.ts` and `src/stories/useYourHookName.story.tsx`, run `yarn start` to start the storybook development server and start coding your hook +1. Create `src/tests/useYourHookName.test.ts`, run `yarn test:watch` to start the test runner in watch mode and start writing tests for your hook 1. Create `src/docs/useYourHookName.md` and create documentation for your hook 1. Export your hook from `src/index.ts` and add your hook to `README.md` From c87e670c0869b61211a02f3fd4ddd7c7033cb59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=9A=E8=87=B4=E8=BF=9C?= Date: Mon, 30 Mar 2020 13:39:39 +0800 Subject: [PATCH 0058/1144] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 08260ad5dc..970b310c59 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,8 +32,8 @@ This library is a collection of React hooks so a proposal for a new hook will ne ### Creating a new hook 1. Create `src/useYourHookName.ts` and `src/stories/useYourHookName.story.tsx`, run `yarn start` to start the storybook development server and start coding your hook -1. Create `src/tests/useYourHookName.test.ts`, run `yarn test:watch` to start the test runner in watch mode and start writing tests for your hook -1. Create `src/docs/useYourHookName.md` and create documentation for your hook +1. Create `tests/useYourHookName.test.ts`, run `yarn test:watch` to start the test runner in watch mode and start writing tests for your hook +1. Create `docs/useYourHookName.md` and create documentation for your hook 1. Export your hook from `src/index.ts` and add your hook to `README.md` You can also write your tests first if you prefer [test-driven development](https://en.wikipedia.org/wiki/Test-driven_development). From 3f8f7a755cbdad6c286d0c59778355b95f386421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=9A=E8=87=B4=E8=BF=9C?= Date: Mon, 30 Mar 2020 13:40:26 +0800 Subject: [PATCH 0059/1144] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 970b310c59..67c03a54ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ This library is a collection of React hooks so a proposal for a new hook will ne ### Creating a new hook -1. Create `src/useYourHookName.ts` and `src/stories/useYourHookName.story.tsx`, run `yarn start` to start the storybook development server and start coding your hook +1. Create `src/useYourHookName.ts` and `stories/useYourHookName.story.tsx`, run `yarn start` to start the storybook development server and start coding your hook 1. Create `tests/useYourHookName.test.ts`, run `yarn test:watch` to start the test runner in watch mode and start writing tests for your hook 1. Create `docs/useYourHookName.md` and create documentation for your hook 1. Export your hook from `src/index.ts` and add your hook to `README.md` From 654f9242b31c0a195d86ec9f8cd8afd84ef45d57 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 Mar 2020 06:12:38 +0000 Subject: [PATCH 0060/1144] chore(deps): update dependency ts-jest to v25.3.0 --- package.json | 2 +- yarn.lock | 33 +++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 98c591f754..7fb3ec69fc 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "rimraf": "3.0.2", "rxjs": "6.5.4", "semantic-release": "17.0.4", - "ts-jest": "25.2.1", + "ts-jest": "25.3.0", "ts-loader": "6.2.2", "ts-node": "8.8.1", "typescript": "3.8.3" diff --git a/yarn.lock b/yarn.lock index a6ff868d44..1ee48b91bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10925,7 +10925,12 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@1.x: + version "1.0.3" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" + integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== + +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -13932,21 +13937,21 @@ semver-regex@^2.0.0: resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@6.x, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - semver@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" @@ -15293,10 +15298,10 @@ ts-easing@^0.2.0: resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== -ts-jest@25.2.1: - version "25.2.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.2.1.tgz#49bf05da26a8b7fbfbc36b4ae2fcdc2fef35c85d" - integrity sha512-TnntkEEjuXq/Gxpw7xToarmHbAafgCaAzOpnajnFC6jI7oo1trMzAHA04eWpc3MhV6+yvhE8uUBAmN+teRJh0A== +ts-jest@25.3.0: + version "25.3.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.3.0.tgz#c12d34573cbe34d49f10567940e44fd19d1c9178" + integrity sha512-qH/uhaC+AFDU9JfAueSr0epIFJkGMvUPog4FxSEVAtPOur1Oni5WBJMiQIkfHvc7PviVRsnlVLLY2I6221CQew== dependencies: bs-logger "0.x" buffer-from "1.x" @@ -15304,10 +15309,10 @@ ts-jest@25.2.1: json5 "2.x" lodash.memoize "4.x" make-error "1.x" - mkdirp "0.x" + mkdirp "1.x" resolve "1.x" - semver "^5.5" - yargs-parser "^16.1.0" + semver "6.x" + yargs-parser "^18.1.1" ts-loader@6.2.2: version "6.2.2" From f05f528b1faf45791fb4b4c931a47063031c0dcc Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 Mar 2020 13:35:55 +0000 Subject: [PATCH 0061/1144] chore(deps): update dependency lint-staged to v10.1.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7fb3ec69fc..bd7644154c 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "jest": "25.2.4", "jest-localstorage-mock": "2.4.0", "keyboardjs": "2.5.1", - "lint-staged": "10.0.10", + "lint-staged": "10.1.0", "markdown-loader": "5.1.0", "prettier": "1.19.1", "raf-stub": "3.0.0", diff --git a/yarn.lock b/yarn.lock index 1ee48b91bc..5624f12972 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10095,10 +10095,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@10.0.10: - version "10.0.10" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.0.10.tgz#d14d33ee02a31a31ad36cf9aa7973fc156c461b5" - integrity sha512-91vNy3eYStExElLWw1Idva5lghKpFaXh9AJqjcyrJXf7AYZrThi4EhQ+GpmiHdPmJJauKhZMMSzQR1bMB90MtA== +lint-staged@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.1.0.tgz#18785bb005d5ed404f1c1db6563e082f7a7baac2" + integrity sha512-WzZ/T+O/aEaaT679sMgI4JqK5mnG69V5KQSouzVsShzZ8wGWte39HT3z61LsxjVNeCf8m/ChhvWJa2wTiQLy5A== dependencies: chalk "^3.0.0" commander "^4.0.1" From 1653a6d9e4fb89110c9be072bab90fee63fe39c5 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 Mar 2020 17:44:17 +0000 Subject: [PATCH 0062/1144] chore(deps): update typescript-eslint monorepo to v2.26.0 --- package.json | 4 ++-- yarn.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index bd7644154c..7d54acbd1e 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,8 @@ "@testing-library/react-hooks": "3.2.1", "@types/jest": "25.1.4", "@types/react": "16.9.11", - "@typescript-eslint/eslint-plugin": "2.25.0", - "@typescript-eslint/parser": "2.25.0", + "@typescript-eslint/eslint-plugin": "2.26.0", + "@typescript-eslint/parser": "2.26.0", "babel-core": "6.26.3", "babel-eslint": "10.1.0", "babel-loader": "8.1.0", diff --git a/yarn.lock b/yarn.lock index 5624f12972..4a8b1c660b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3426,40 +3426,40 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.25.0.tgz#0b60917332f20dcff54d0eb9be2a9e9f4c9fbd02" - integrity sha512-W2YyMtjmlrOjtXc+FtTelVs9OhuR6OlYc4XKIslJ8PUJOqgYYAPRJhAqkYRQo3G4sjvG8jSodsNycEn4W2gHUw== +"@typescript-eslint/eslint-plugin@2.26.0": + version "2.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.26.0.tgz#04c96560c8981421e5a9caad8394192363cc423f" + integrity sha512-4yUnLv40bzfzsXcTAtZyTjbiGUXMrcIJcIMioI22tSOyAxpdXiZ4r7YQUU8Jj6XXrLz9d5aMHPQf5JFR7h27Nw== dependencies: - "@typescript-eslint/experimental-utils" "2.25.0" + "@typescript-eslint/experimental-utils" "2.26.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.25.0.tgz#13691c4fe368bd377b1e5b1e4ad660b220bf7714" - integrity sha512-0IZ4ZR5QkFYbaJk+8eJ2kYeA+1tzOE1sBjbwwtSV85oNWYUBep+EyhlZ7DLUCyhMUGuJpcCCFL0fDtYAP1zMZw== +"@typescript-eslint/experimental-utils@2.26.0": + version "2.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.26.0.tgz#063390c404d9980767d76274df386c0aa675d91d" + integrity sha512-RELVoH5EYd+JlGprEyojUv9HeKcZqF7nZUGSblyAw1FwOGNnmQIU8kxJ69fttQvEwCsX5D6ECJT8GTozxrDKVQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.25.0" + "@typescript-eslint/typescript-estree" "2.26.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.25.0.tgz#abfb3d999084824d9a756d9b9c0f36fba03adb76" - integrity sha512-mccBLaBSpNVgp191CP5W+8U1crTyXsRziWliCqzj02kpxdjKMvFHGJbK33NroquH3zB/gZ8H511HEsJBa2fNEg== +"@typescript-eslint/parser@2.26.0": + version "2.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.26.0.tgz#385463615818b33acb72a25b39c03579df93d76f" + integrity sha512-+Xj5fucDtdKEVGSh9353wcnseMRkPpEAOY96EEenN7kJVrLqy/EVwtIh3mxcUz8lsFXW1mT5nN5vvEam/a5HiQ== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.25.0" - "@typescript-eslint/typescript-estree" "2.25.0" + "@typescript-eslint/experimental-utils" "2.26.0" + "@typescript-eslint/typescript-estree" "2.26.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz#b790497556734b7476fa7dd3fa539955a5c79e2c" - integrity sha512-VUksmx5lDxSi6GfmwSK7SSoIKSw9anukWWNitQPqt58LuYrKalzsgeuignbqnB+rK/xxGlSsCy8lYnwFfB6YJg== +"@typescript-eslint/typescript-estree@2.26.0": + version "2.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.26.0.tgz#d8132cf1ee8a72234f996519a47d8a9118b57d56" + integrity sha512-3x4SyZCLB4zsKsjuhxDLeVJN6W29VwBnYpCsZ7vIdPel9ZqLfIZJgJXO47MNUkurGpQuIBALdPQKtsSnWpE1Yg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" From 1edfdd5ece75ff97b6d689ea8c11be159e33aa35 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 23 Mar 2020 22:54:48 +0000 Subject: [PATCH 0063/1144] chore(deps): update dependency prettier to v2 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7d54acbd1e..5178449286 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "keyboardjs": "2.5.1", "lint-staged": "10.1.0", "markdown-loader": "5.1.0", - "prettier": "1.19.1", + "prettier": "2.0.2", "raf-stub": "3.0.0", "react": "16.13.1", "react-dom": "16.13.1", diff --git a/yarn.lock b/yarn.lock index 4a8b1c660b..a64c9cfa3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12422,10 +12422,10 @@ prepend-http@^1.0.0, prepend-http@^1.0.1: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prettier@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +prettier@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" + integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== pretty-error@^2.1.1: version "2.1.1" From a976a94023e741892b6519ef2fc96bbf678ba903 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2020 09:07:30 +0000 Subject: [PATCH 0064/1144] chore(deps): bump acorn from 5.7.3 to 5.7.4 Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a64c9cfa3c..6db2cd6d58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3675,9 +3675,9 @@ acorn-walk@^6.0.1: integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== acorn@^5.5.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== acorn@^6.0.1, acorn@^6.2.1: version "6.3.0" From eda9706b44b7de99426d54cb160ff73368843c27 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 Mar 2020 16:32:51 +0000 Subject: [PATCH 0065/1144] chore(deps): update dependency @testing-library/react to v10 --- package.json | 2 +- yarn.lock | 74 +++++++++++++++++++++++++--------------------------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 5178449286..b2162aa8a2 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "@storybook/addon-notes": "5.3.17", "@storybook/addon-options": "5.3.17", "@storybook/react": "5.3.17", - "@testing-library/react": "9.5.0", + "@testing-library/react": "10.0.2", "@testing-library/react-hooks": "3.2.1", "@types/jest": "25.1.4", "@types/react": "16.9.11", diff --git a/yarn.lock b/yarn.lock index 6db2cd6d58..a9f4cf2bc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1799,6 +1799,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.9.2": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.7.0": version "7.7.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.0.tgz#4fadc1b8e734d97f56de39c77de76f2562e597d0" @@ -2589,11 +2596,6 @@ lodash "^4.17.4" read-pkg-up "^7.0.0" -"@sheerun/mutationobserver-shim@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" - integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== - "@shopify/async@^2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@shopify/async/-/async-2.1.4.tgz#5e326240a161c20387b2939e7e875ebb11de56de" @@ -3126,18 +3128,16 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" -"@testing-library/dom@^6.15.0": - version "6.15.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.15.0.tgz#042abea7b4685b70d9a919100da9024507dc20bb" - integrity sha512-8N24c4XwOigPicwc8n4ECgEoJW2/mMzRJBxu4Uo0zhLERZTbNzqpL5fyCigu7JGUXX+ITuiK4z9/lnHbYRHLwQ== +"@testing-library/dom@^7.1.0": + version "7.1.4" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.1.4.tgz#affb29bd303b01eb0937debbbc5671dac640086b" + integrity sha512-eifMaV8NW4xk/moC4+ga8Jpl6MNLNjQgBgimDBqmcZPVyDPcWYsapAMcp1HIGpVsTNAXBVB8ILza3uyu86v4ig== dependencies: - "@babel/runtime" "^7.8.4" - "@sheerun/mutationobserver-shim" "^0.3.2" - "@types/testing-library__dom" "^6.12.1" + "@babel/runtime" "^7.9.2" + "@types/testing-library__dom" "^7.0.0" aria-query "^4.0.2" - dom-accessibility-api "^0.3.0" + dom-accessibility-api "^0.4.2" pretty-format "^25.1.0" - wait-for-expect "^3.0.2" "@testing-library/react-hooks@3.2.1": version "3.2.1" @@ -3147,14 +3147,14 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.0.0" -"@testing-library/react@9.5.0": - version "9.5.0" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.5.0.tgz#71531655a7890b61e77a1b39452fbedf0472ca5e" - integrity sha512-di1b+D0p+rfeboHO5W7gTVeZDIK5+maEgstrZbWZSSvxDyfDRkkyBE1AJR5Psd6doNldluXlCWqXriUfqu/9Qg== +"@testing-library/react@10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.2.tgz#8eca7aa52d810cf7150048a2829fdc487162006d" + integrity sha512-YT6Mw0oJz7R6vlEkmo1FlUD+K15FeXApOB5Ffm9zooFVnrwkt00w18dUJFMOh1yRp9wTdVRonbor7o4PIpFCmA== dependencies: - "@babel/runtime" "^7.8.4" - "@testing-library/dom" "^6.15.0" - "@types/testing-library__react" "^9.1.2" + "@babel/runtime" "^7.9.2" + "@testing-library/dom" "^7.1.0" + "@types/testing-library__react" "^10.0.0" "@types/babel__core@^7.1.0": version "7.1.3" @@ -3379,12 +3379,12 @@ dependencies: pretty-format "^24.3.0" -"@types/testing-library__dom@^6.12.1": - version "6.12.1" - resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-6.12.1.tgz#37af28fae051f9e3feed5684535b1540c97ae28b" - integrity sha512-cgqnEjxKk31tQt29j4baSWaZPNjQf3bHalj2gcHQTpW5SuHRal76gOpF0vypeEo6o+sS5inOvvNdzLY0B3FB2A== +"@types/testing-library__dom@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.0.0.tgz#c0fb7d1c2495a3d26f19342102142d47500f0319" + integrity sha512-1TEPWyqQ6IQ7R1hCegZmFSA3KrBQjdzJW7yC9ybpRcFst5XuPOqBGNr0mTAKbxwI/TrTyc1skeyLJrpcvAf93w== dependencies: - pretty-format "^24.3.0" + pretty-format "^25.1.0" "@types/testing-library__react-hooks@^3.0.0": version "3.1.0" @@ -3394,13 +3394,14 @@ "@types/react" "*" "@types/react-test-renderer" "*" -"@types/testing-library__react@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-9.1.2.tgz#e33af9124c60a010fc03a34eff8f8a34a75c4351" - integrity sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q== +"@types/testing-library__react@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-10.0.0.tgz#8413a47f435bf7ce50bbc1b6d119300d39aed5bd" + integrity sha512-aByqRiRn9psCWbgW7a+gfW/LUQY/ChznnuPyWwLipcJm+rXaLNeYM4qL21jWPGn9W1H//oXgLE9aDlpkZSY3CQ== dependencies: "@types/react-dom" "*" "@types/testing-library__dom" "*" + pretty-format "^25.1.0" "@types/webpack-env@^1.15.0": version "1.15.0" @@ -6371,10 +6372,10 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz#511e5993dd673b97c87ea47dba0e3892f7e0c983" - integrity sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA== +dom-accessibility-api@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c" + integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA== dom-converter@^0.2: version "0.2.0" @@ -15782,11 +15783,6 @@ w3c-xmlserializer@^1.1.2: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" -wait-for-expect@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463" - integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag== - walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" From 34b303bbde23c0d3a171f61c987fc597e8a60f95 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 31 Mar 2020 13:47:30 +0000 Subject: [PATCH 0066/1144] chore(deps): update storybook monorepo to v5.3.18 --- package.json | 10 +- yarn.lock | 268 +++++++++++++++++++++++++-------------------------- 2 files changed, 139 insertions(+), 139 deletions(-) diff --git a/package.json b/package.json index 8911e07cc4..60a6dedc7d 100644 --- a/package.json +++ b/package.json @@ -74,11 +74,11 @@ "@semantic-release/git": "9.0.0", "@semantic-release/npm": "7.0.5", "@shopify/jest-dom-mocks": "2.8.11", - "@storybook/addon-actions": "5.3.17", - "@storybook/addon-knobs": "5.3.17", - "@storybook/addon-notes": "5.3.17", - "@storybook/addon-options": "5.3.17", - "@storybook/react": "5.3.17", + "@storybook/addon-actions": "5.3.18", + "@storybook/addon-knobs": "5.3.18", + "@storybook/addon-notes": "5.3.18", + "@storybook/addon-options": "5.3.18", + "@storybook/react": "5.3.18", "@testing-library/react": "10.0.2", "@testing-library/react-hooks": "3.2.1", "@types/jest": "25.1.4", diff --git a/yarn.lock b/yarn.lock index 54fcdcc460..4bf6de0842 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2634,17 +2634,17 @@ dependencies: type-detect "4.0.8" -"@storybook/addon-actions@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.3.17.tgz#ec7ae8fa25ef211c2a3302b6ac1d271a6247f767" - integrity sha512-06HQSBqWFyXcqV418Uv3oMHomNy9g3uCt0FHrqY3BAc7PldY1X0tW65oy//uBueaRaYKdhtRrrjfXRaPQWmDbA== - dependencies: - "@storybook/addons" "5.3.17" - "@storybook/api" "5.3.17" - "@storybook/client-api" "5.3.17" - "@storybook/components" "5.3.17" - "@storybook/core-events" "5.3.17" - "@storybook/theming" "5.3.17" +"@storybook/addon-actions@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.3.18.tgz#e3e3b1475cebc9bdd2d563822fba9ac662b2601a" + integrity sha512-jdBVCcfyWin274Lkwg5cL+1fJ651NCuIWxuJVsmHQtIl2xTjf2MyoMoKQZNdt4xtE+W9w+rS4bYt04elrizThg== + dependencies: + "@storybook/addons" "5.3.18" + "@storybook/api" "5.3.18" + "@storybook/client-api" "5.3.18" + "@storybook/components" "5.3.18" + "@storybook/core-events" "5.3.18" + "@storybook/theming" "5.3.18" core-js "^3.0.1" fast-deep-equal "^2.0.1" global "^4.3.2" @@ -2654,17 +2654,17 @@ react-inspector "^4.0.0" uuid "^3.3.2" -"@storybook/addon-knobs@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.3.17.tgz#5c255a1369bfec898c2a6ea7de904b3eeb7a31d1" - integrity sha512-SMjvH3EjUbt4Xn1Q22thXVQSDrJRUB3IAzhUNdBovFB3RwOXmRFd0iSHC3TTKJfW2TYPssl8cnNGQTH6O2d14g== - dependencies: - "@storybook/addons" "5.3.17" - "@storybook/api" "5.3.17" - "@storybook/client-api" "5.3.17" - "@storybook/components" "5.3.17" - "@storybook/core-events" "5.3.17" - "@storybook/theming" "5.3.17" +"@storybook/addon-knobs@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.3.18.tgz#60365bb179699cd8dc91df8be947469266012847" + integrity sha512-X0WxGKoso3j5mS4c4enM8BvCjbO6Wwfxc++swQTqtANpBZ8k+w0piiEF1fiJf+ssgEAWe5brgIqnQ9kiBGLqKA== + dependencies: + "@storybook/addons" "5.3.18" + "@storybook/api" "5.3.18" + "@storybook/client-api" "5.3.18" + "@storybook/components" "5.3.18" + "@storybook/core-events" "5.3.18" + "@storybook/theming" "5.3.18" "@types/react-color" "^3.0.1" copy-to-clipboard "^3.0.8" core-js "^3.0.1" @@ -2678,18 +2678,18 @@ react-lifecycles-compat "^3.0.4" react-select "^3.0.8" -"@storybook/addon-notes@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/addon-notes/-/addon-notes-5.3.17.tgz#717ffc552e34d051ed8bf2ca61a6743a9628be24" - integrity sha512-Jmsgz3SXjn3j1YjK50ubJiN6N62Tl7W54WzKPMq9mvu/t8egYBDn0JjTkmn8QMbcXn4JL3Qs7W56uRFj0KewIg== - dependencies: - "@storybook/addons" "5.3.17" - "@storybook/api" "5.3.17" - "@storybook/client-logger" "5.3.17" - "@storybook/components" "5.3.17" - "@storybook/core-events" "5.3.17" - "@storybook/router" "5.3.17" - "@storybook/theming" "5.3.17" +"@storybook/addon-notes@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/addon-notes/-/addon-notes-5.3.18.tgz#dc8be958bbb082188c529aa31853e8cad389a642" + integrity sha512-P8d5P6+MErodRz2whP1f33ovhGMcOc/g4a+8qatiUotsPbg/2LKNRlY1QSfHWtm26NIGsgOTqSb0z1q6cyMNvQ== + dependencies: + "@storybook/addons" "5.3.18" + "@storybook/api" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/components" "5.3.18" + "@storybook/core-events" "5.3.18" + "@storybook/router" "5.3.18" + "@storybook/theming" "5.3.18" core-js "^3.0.1" global "^4.3.2" markdown-to-jsx "^6.10.3" @@ -2697,40 +2697,40 @@ prop-types "^15.7.2" util-deprecate "^1.0.2" -"@storybook/addon-options@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-5.3.17.tgz#971d22f9902420cd6d16e1f78c219ebfb5f850a5" - integrity sha512-6Cm8czEIg+8FnCMfj/cI8q1nl8Khlxhe4sYw5fwWXRKbOld6PBVhnTUqWv2lWzlkxpbDgVeUJYnMjXsza/RNaA== +"@storybook/addon-options@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-5.3.18.tgz#cd40fa137213e4f738691771c404a255eee5b39a" + integrity sha512-AlejrKB0oiG/tVpqziA0zyKa9KhIwL7xGZYy+kdgTmXiWVJzp4OQgxaOrVD6trPrPpx8/YIwe5ivClxD5HuUpA== dependencies: - "@storybook/addons" "5.3.17" + "@storybook/addons" "5.3.18" core-js "^3.0.1" util-deprecate "^1.0.2" -"@storybook/addons@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.3.17.tgz#8efab65904040b0b8578eedc9a5772dbcbf6fa83" - integrity sha512-zg6O1bmffRsHXJOWAnSD2O3tPnVMoD8Yfu+a5zBVXDiUP1E/TGzgjjjYBUUCU3yQg1Ted5rIn4o6ql/rZNNlgA== +"@storybook/addons@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.3.18.tgz#5cbba6407ef7a802041c5ee831473bc3bed61f64" + integrity sha512-ZQjDgTUDFRLvAiBg2d8FgPgghfQ+9uFyXQbtiGlTBLinrPCeQd7J86qiUES0fcGoohCCw0wWKtvB0WF2z1XNDg== dependencies: - "@storybook/api" "5.3.17" - "@storybook/channels" "5.3.17" - "@storybook/client-logger" "5.3.17" - "@storybook/core-events" "5.3.17" + "@storybook/api" "5.3.18" + "@storybook/channels" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/core-events" "5.3.18" core-js "^3.0.1" global "^4.3.2" util-deprecate "^1.0.2" -"@storybook/api@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.3.17.tgz#1c0dad3309afef6b0a5585cb59c65824fb4d2721" - integrity sha512-G40jtXFY10hQo6GSw5JeFYt41loD4+7s0uU18Rm6lfa/twOgp6vqqyDCWDvpRRxRBB5uDIKKHLt13X9gWe8tQQ== +"@storybook/api@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.3.18.tgz#95582ab90d947065e0e34ed603650a3630dcbd16" + integrity sha512-QXaccNCARHzPWOuxYndiebGWBZmwiUvRgB9ji0XTJBS3y8K0ZPb5QyuqiKPaEWUj8dBA8rzdDtkW3Yt95Namaw== dependencies: "@reach/router" "^1.2.1" - "@storybook/channels" "5.3.17" - "@storybook/client-logger" "5.3.17" - "@storybook/core-events" "5.3.17" + "@storybook/channels" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/core-events" "5.3.18" "@storybook/csf" "0.0.1" - "@storybook/router" "5.3.17" - "@storybook/theming" "5.3.17" + "@storybook/router" "5.3.18" + "@storybook/theming" "5.3.18" "@types/reach__router" "^1.2.3" core-js "^3.0.1" fast-deep-equal "^2.0.1" @@ -2745,34 +2745,34 @@ telejson "^3.2.0" util-deprecate "^1.0.2" -"@storybook/channel-postmessage@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.3.17.tgz#807b6316cd0e52d9f27363d5092ad1cd896b694c" - integrity sha512-1aSQNeO2+roPRgMFjW3AWTO3uS93lbCMUTYCBdi20md4bQ9SutJy33rynCQcWuMj1prCQ2Ekz4BGhdcIQVKlzg== +"@storybook/channel-postmessage@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.3.18.tgz#93d46740b5cc9b36ddd073f0715b54c4959953bf" + integrity sha512-awxBW/aVfNtY9QvYZgsPaMXgUpC2+W3vEyQcl/w4ce0YVH+7yWx3wt3Ku49lQwxZwDrxP3QoC0U+mkPc9hBJwA== dependencies: - "@storybook/channels" "5.3.17" - "@storybook/client-logger" "5.3.17" + "@storybook/channels" "5.3.18" + "@storybook/client-logger" "5.3.18" core-js "^3.0.1" global "^4.3.2" telejson "^3.2.0" -"@storybook/channels@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.3.17.tgz#74eccb10c2395499da6a290bcd0272d6d6c7c5b2" - integrity sha512-5hlBRbyk+YxC4KgecYG8wWwB2v1BzRJXhSlemFDOQk9wx37gVpne+rBydEtNFO4InmaZf6tKbBcpH0wBFLdWYA== +"@storybook/channels@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.3.18.tgz#490c9eaa8292b0571c0f665052b12addf7c35f21" + integrity sha512-scP/6td/BJSEOgfN+qaYGDf3E793xye7tIw6W+sYqwg+xdMFO39wVXgVZNpQL6sLEwpJZTaPywCjC6p6ksErqQ== dependencies: core-js "^3.0.1" -"@storybook/client-api@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.3.17.tgz#fc1d247caf267ebcc6ddf957fca7e02ae752d99e" - integrity sha512-oe55FPTGVL2k+j45eCN3oE7ePkE4VpgUQ/dhJbjU0R2L+HyRyBhd0wnMYj1f5E8uVNbtjFYAtbjjgcf1R1imeg== +"@storybook/client-api@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.3.18.tgz#e71041796f95888de0e4524734418e6b120b060a" + integrity sha512-QiXTDUpjdyW19BlocLw07DrkOnEzVaWGJcRze2nSs29IKKuq1Ncv2LOAZt6ySSq0PmIKsjBou3bmS1/aXmDMdw== dependencies: - "@storybook/addons" "5.3.17" - "@storybook/channel-postmessage" "5.3.17" - "@storybook/channels" "5.3.17" - "@storybook/client-logger" "5.3.17" - "@storybook/core-events" "5.3.17" + "@storybook/addons" "5.3.18" + "@storybook/channel-postmessage" "5.3.18" + "@storybook/channels" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/core-events" "5.3.18" "@storybook/csf" "0.0.1" "@types/webpack-env" "^1.15.0" core-js "^3.0.1" @@ -2786,20 +2786,20 @@ ts-dedent "^1.1.0" util-deprecate "^1.0.2" -"@storybook/client-logger@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.3.17.tgz#bf9c7ef52da75a5c1f2c5d74724442224deea6e4" - integrity sha512-GYYvVGIOs+fq11LXXy7x2sr3hhC9LMI1jtIckjKV1dsY9MJ5g22M+Wl5Iw4nf6VMWsqcN9LSlYE+u/H+Q2uCHw== +"@storybook/client-logger@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.3.18.tgz#27c9d09d788965db0164be6e168bc3f03adbf88f" + integrity sha512-RZjxw4uqZX3Yk27IirbB/pQG+wRsQSSRlKqYa8KQ5bSanm4IrcV9VA1OQbuySW9njE+CexAnakQJ/fENdmurNg== dependencies: core-js "^3.0.1" -"@storybook/components@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.3.17.tgz#287430fc9c5f59b1d3590b50b3c7688355b22639" - integrity sha512-M5oqbzcqFX4VDNI8siT3phT7rmFwChQ/xPwX9ygByBsZCoNuLMzafavfTOhZvxCPiliFbBxmxtK/ibCsSzuKZg== +"@storybook/components@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.3.18.tgz#528f6ab1660981e948993a04b407a6fad7751589" + integrity sha512-LIN4aVCCDY7klOwtuqQhfYz4tHaMADhXEzZpij+3r8N68Inck6IJ1oo9A9umXQPsTioQi8e6FLobH1im90j/2A== dependencies: - "@storybook/client-logger" "5.3.17" - "@storybook/theming" "5.3.17" + "@storybook/client-logger" "5.3.18" + "@storybook/theming" "5.3.18" "@types/react-syntax-highlighter" "11.0.4" "@types/react-textarea-autosize" "^4.3.3" core-js "^3.0.1" @@ -2820,33 +2820,33 @@ simplebar-react "^1.0.0-alpha.6" ts-dedent "^1.1.0" -"@storybook/core-events@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.3.17.tgz#698ce0a36c29fe8fa04608f56ccca53aa1d31638" - integrity sha512-DOeX9fpeGW4o9Gocxa4VW9wAlAyfIVNDTzq0wVvvMBthTTo9u58NmndglEMDgDa2Cq6iAIPh7vz2bRJCNexzLw== +"@storybook/core-events@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.3.18.tgz#e5d335f8a2c7dd46502b8f505006f1e111b46d49" + integrity sha512-uQ6NYJ5WODXK8DJ7m8y3yUAtWB3n+6XtYztjY+tdkCsLYvTYDXNS+epV+f5Hu9+gB+/Dm+b5Su4jDD+LZB2QWA== dependencies: core-js "^3.0.1" -"@storybook/core@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.3.17.tgz#abd09dc416f87c7954ef3615bc3f4898c93e2b45" - integrity sha512-H6G8ygjb4RSVSKPdWz6su3Nvzxm8CfrHuCyUo4DLC46mirXfYRrJV1HiwXriViqoZV4gFbpaNKTDzTl/QKFDAg== +"@storybook/core@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.3.18.tgz#3f3c0498275826c1cc4368aba203ac17a6ae5c9c" + integrity sha512-XQb/UQb+Ohuaw0GhKKYzvmuuh5Tit93f2cLZD9QCSWUPvDGmLG5g91Y9NbUr4Ap3mANT3NksMNhkAV0GxExEkg== dependencies: "@babel/plugin-proposal-class-properties" "^7.7.0" "@babel/plugin-proposal-object-rest-spread" "^7.6.2" "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/preset-env" "^7.4.5" - "@storybook/addons" "5.3.17" - "@storybook/channel-postmessage" "5.3.17" - "@storybook/client-api" "5.3.17" - "@storybook/client-logger" "5.3.17" - "@storybook/core-events" "5.3.17" + "@storybook/addons" "5.3.18" + "@storybook/channel-postmessage" "5.3.18" + "@storybook/client-api" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/core-events" "5.3.18" "@storybook/csf" "0.0.1" - "@storybook/node-logger" "5.3.17" - "@storybook/router" "5.3.17" - "@storybook/theming" "5.3.17" - "@storybook/ui" "5.3.17" + "@storybook/node-logger" "5.3.18" + "@storybook/router" "5.3.18" + "@storybook/theming" "5.3.18" + "@storybook/ui" "5.3.18" airbnb-js-shims "^2.2.1" ansi-to-html "^0.6.11" autoprefixer "^9.7.2" @@ -2913,10 +2913,10 @@ dependencies: lodash "^4.17.15" -"@storybook/node-logger@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.3.17.tgz#f3ad5bf9dd74d8e1cdfb8d831d66a80c5039cf4c" - integrity sha512-onfcxl37BYZI1HGuPI9MelkyUWjn7NpfN8RUYdqG9P6WKiIY5xbpG0V6qod5jvIKIypK0NmfJTtneOu46L/oDg== +"@storybook/node-logger@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.3.18.tgz#ee278acb8b6f10d456a24c0ff6d59818a0c3ad94" + integrity sha512-Go/hdtaPTtjgJP+GYk8VXcOmecrdG7cXm0yyTlatd6s8xXI0txHme1/0MOZmEPows1Ec7KAQ20+NnaCGUPZUUg== dependencies: "@types/npmlog" "^4.1.2" chalk "^3.0.0" @@ -2925,17 +2925,17 @@ pretty-hrtime "^1.0.3" regenerator-runtime "^0.13.3" -"@storybook/react@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.3.17.tgz#403b58606211a9ca87d40e38d55186b3e088640d" - integrity sha512-FQLH3q2Ge68oLBaTge7wl5Y1KkB+pqL36llor7TOO9IxGLF6o2t2qillWnrgX6yZUpkvJK8MgkZW1/N3tslw4Q== +"@storybook/react@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.3.18.tgz#c057b680924e188d44149c3d67dd31aead88b28a" + integrity sha512-6yNg+phcrEqEjC2NOiu0mJuxbTwX7yzbkcusIn0S7N/KTXNO7CGvYjAkdjfw0gTLjfuVDZIjDQfoosslvfsj3w== dependencies: "@babel/plugin-transform-react-constant-elements" "^7.6.3" "@babel/preset-flow" "^7.0.0" "@babel/preset-react" "^7.0.0" - "@storybook/addons" "5.3.17" - "@storybook/core" "5.3.17" - "@storybook/node-logger" "5.3.17" + "@storybook/addons" "5.3.18" + "@storybook/core" "5.3.18" + "@storybook/node-logger" "5.3.18" "@svgr/webpack" "^4.0.3" "@types/webpack-env" "^1.15.0" babel-plugin-add-react-displayname "^0.0.5" @@ -2952,10 +2952,10 @@ ts-dedent "^1.1.0" webpack "^4.33.0" -"@storybook/router@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.3.17.tgz#4db96b45f39b25a3f7a4e2899c36e7e9e4ba6108" - integrity sha512-ANsiehGRTVSremgTW0Vt47dQ4JA86a4/w/4G6QqHU8Cm4jO3cw/wAcCxlzfcgCXOUiq+SuyPTU43+0O5uBx33g== +"@storybook/router@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.3.18.tgz#8ab22f1f2f7f957e78baf992030707a62289076e" + integrity sha512-6B2U2C75KTSVaCuYYgcubeJGcCSnwsXuEf50hEd5mGqWgHZfojCtGvB7Ko4X+0h8rEC+eNA4p7YBOhlUv9WNrQ== dependencies: "@reach/router" "^1.2.1" "@storybook/csf" "0.0.1" @@ -2967,14 +2967,14 @@ qs "^6.6.0" util-deprecate "^1.0.2" -"@storybook/theming@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.3.17.tgz#cf6278c4857229c7167faf04d5b2206bc5ee04e1" - integrity sha512-4JeOZnDDHtb4LOt5sXe/s1Jhbb2UPsr8zL9NWmKJmTsgnyTvBipNHOmFYDUsIacB5K4GXSqm+cZ7Z4AkUgWCDw== +"@storybook/theming@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.3.18.tgz#35e78de79d9cf8f1248af0dd1c7fa60555761312" + integrity sha512-lfFTeLoYwLMKg96N3gn0umghMdAHgJBGuk2OM8Ll84yWtdl9RGnzfiI1Fl7Cr5k95dCF7drLJlJCao1VxUkFSA== dependencies: "@emotion/core" "^10.0.20" "@emotion/styled" "^10.0.17" - "@storybook/client-logger" "5.3.17" + "@storybook/client-logger" "5.3.18" core-js "^3.0.1" deep-object-diff "^1.1.0" emotion-theming "^10.0.19" @@ -2985,20 +2985,20 @@ resolve-from "^5.0.0" ts-dedent "^1.1.0" -"@storybook/ui@5.3.17": - version "5.3.17" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.3.17.tgz#2d47617896a2d928fb79dc8a0e709cee9b57cc50" - integrity sha512-5S9r70QbtNKu8loa5pfO5lLX9coF/ZqesEKcanfvuSwqCSg/Z51UwFCuO6eNhVlpXzyZXi5d8qKbZlbf+uvDAA== +"@storybook/ui@5.3.18": + version "5.3.18" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.3.18.tgz#c66f6d94a3c50bb706f4d5b1d5592439110f16f0" + integrity sha512-xyXK53fNe9lkGPmXf3Nk+n0gz9gOgXI+fDxetyDLpX79k3DIN/jCKEnv45vXof7OQ45mTmyBvUNTKrNLqKTt5Q== dependencies: "@emotion/core" "^10.0.20" - "@storybook/addons" "5.3.17" - "@storybook/api" "5.3.17" - "@storybook/channels" "5.3.17" - "@storybook/client-logger" "5.3.17" - "@storybook/components" "5.3.17" - "@storybook/core-events" "5.3.17" - "@storybook/router" "5.3.17" - "@storybook/theming" "5.3.17" + "@storybook/addons" "5.3.18" + "@storybook/api" "5.3.18" + "@storybook/channels" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/components" "5.3.18" + "@storybook/core-events" "5.3.18" + "@storybook/router" "5.3.18" + "@storybook/theming" "5.3.18" copy-to-clipboard "^3.0.8" core-js "^3.0.1" core-js-pure "^3.0.1" From 764f6a0e211fc3cc6acb31a310b109525c692efb Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 31 Mar 2020 14:12:38 +0000 Subject: [PATCH 0067/1144] chore(deps): update dependency lint-staged to v10.1.1 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 60a6dedc7d..038a009e75 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "jest": "25.2.4", "jest-localstorage-mock": "2.4.0", "keyboardjs": "2.5.1", - "lint-staged": "10.1.0", + "lint-staged": "10.1.1", "markdown-loader": "5.1.0", "prettier": "2.0.2", "raf-stub": "3.0.0", diff --git a/yarn.lock b/yarn.lock index 4bf6de0842..0473afb071 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10096,10 +10096,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.1.0.tgz#18785bb005d5ed404f1c1db6563e082f7a7baac2" - integrity sha512-WzZ/T+O/aEaaT679sMgI4JqK5mnG69V5KQSouzVsShzZ8wGWte39HT3z61LsxjVNeCf8m/ChhvWJa2wTiQLy5A== +lint-staged@10.1.1: + version "10.1.1" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.1.1.tgz#1c8569b66d684e6e3553cd760c03053f41fca152" + integrity sha512-wAeu/ePaBAOfwM2+cVbgPWDtn17B0Sxiv0NvNEqDAIvB8Yhvl60vafKFiK4grcYn87K1iK+a0zVoETvKbdT9/Q== dependencies: chalk "^3.0.0" commander "^4.0.1" From 1ef1272d6dbe8fbcc2d08223cd80ef32ce28a9c9 Mon Sep 17 00:00:00 2001 From: xobotyi Date: Wed, 1 Apr 2020 08:49:05 +0300 Subject: [PATCH 0068/1144] feat(useRafLoop): implement #1090 --- docs/useRafLoop.md | 2 +- src/useRafLoop.ts | 5 ++--- tests/useRafLoop.test.tsx | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/useRafLoop.md b/docs/useRafLoop.md index f124385b91..978c5587ac 100644 --- a/docs/useRafLoop.md +++ b/docs/useRafLoop.md @@ -31,5 +31,5 @@ const Demo = () => { ```ts const [stopLoop, isActive, startLoop] = useRafLoop(callback: CallableFunction, deps?: DependencyList); ``` -* `callback` — function to call each RAF tick +* `callback(time: number)` — function to call each RAF tick diff --git a/src/useRafLoop.ts b/src/useRafLoop.ts index 0eafd9a93a..9760ea6b7b 100644 --- a/src/useRafLoop.ts +++ b/src/useRafLoop.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ import { useEffect, useRef, useState } from 'react'; export type RafLoopReturns = [() => void, boolean, () => void]; @@ -7,8 +6,8 @@ export default function useRafLoop(callback: CallableFunction): RafLoopReturns { const raf = useRef(null); const [isActive, setIsActive] = useState(true); - function loopStep() { - callback(); + function loopStep(time: number) { + callback(time); raf.current = requestAnimationFrame(loopStep); } diff --git a/tests/useRafLoop.test.tsx b/tests/useRafLoop.test.tsx index 844c7879a9..36f7d4466f 100644 --- a/tests/useRafLoop.test.tsx +++ b/tests/useRafLoop.test.tsx @@ -99,4 +99,20 @@ describe('useRafLoop', () => { expect(spy).not.toBeCalled(); }); + + it('should pass timestamp as 1st argument of callback', () => { + const spy = jest.fn(); + const hook = renderHook(() => useRafLoop(spy), { initialProps: false }); + + requestAnimationFrame.step(); + + act(() => { + hook.result.current[0](); + }); + + requestAnimationFrame.step(); + + expect(spy).toHaveBeenCalled(); + expect(typeof spy.mock.calls[0][0]).toBe('number'); + }); }); From baa2f7511e18fc9fec29376afa27af73de633d8f Mon Sep 17 00:00:00 2001 From: xobotyi Date: Wed, 1 Apr 2020 10:41:36 +0300 Subject: [PATCH 0069/1144] feat(useRafLoop): reworked the hook, now it do not re-render parent component. BREAKING CHANGE: changed return array, now it returns only functions in next order: [stop, start, isActive]. Parent component is not re-rendered on loop start/stop. --- docs/useRafLoop.md | 33 ++++++--- package.json | 2 +- src/useRafLoop.ts | 65 ++++++++++------- stories/useRafLoop.story.tsx | 15 +++- tests/useRafLoop.test.tsx | 137 ++++++++++++++++++++++------------- 5 files changed, 158 insertions(+), 94 deletions(-) diff --git a/docs/useRafLoop.md b/docs/useRafLoop.md index 978c5587ac..a326600728 100644 --- a/docs/useRafLoop.md +++ b/docs/useRafLoop.md @@ -1,26 +1,35 @@ # `useRafLoop` -React hook that calls given function inside the RAF loop without re-rendering parent component if not needed. Loop stops automatically on component unmount. -Provides controls to stop and start loop manually. +This hook call given function within the RAF loop without re-rendering parent component. +Loop stops automatically on component unmount. + +Additionally hook provides methods to start/stop loop and check current state. ## Usage ```jsx import * as React from 'react'; -import { useRafLoop } from 'react-use'; +import { useRafLoop, useUpdate } from 'react-use'; const Demo = () => { const [ticks, setTicks] = React.useState(0); + const [lastCall, setLastCall] = React.useState(0); + const update = useUpdate(); - const [loopStop, isActive, loopStart] = useRafLoop(() => { - setTicks(ticks + 1); - }, [ticks]); + const [loopStop, loopStart, isActive] = useRafLoop((time) => { + setTicks(ticks => ticks + 1); + setLastCall(time); + }); return (
RAF triggered: {ticks} (times)
+
Last high res timestamp: {lastCall}

- +
); }; @@ -29,7 +38,13 @@ const Demo = () => { ## Reference ```ts -const [stopLoop, isActive, startLoop] = useRafLoop(callback: CallableFunction, deps?: DependencyList); +const [stopLoop, startLoop, isActive] = useRafLoop(callback: FrameRequestCallback, initiallyActive = true); ``` -* `callback(time: number)` — function to call each RAF tick +* **`callback`**_: `(time: number)=>void`_ — function to call each RAF tick. + * **`time`**_: `number`_ — DOMHighResTimeStamp, which indicates the current time (based on the number of milliseconds since time origin). +* **`initiallyActive`**_: `boolean`_ — whether loop should be started at initial render. +* Return + * **`stopLoop`**_: `()=>void`_ — stop loop if it is active. + * **`startLoop`**_: `()=>void`_ — start loop if it was inactive. + * **`isActive`**_: `()=>boolean`_ — _true_ if loop is active. diff --git a/package.json b/package.json index 7d54acbd1e..e1187107aa 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "jest --maxWorkers 2", "test:watch": "jest --watch", "test:coverage": "jest --coverage", - "lint": "eslint '{src,tests}/**/*.{ts,tsx}'", + "lint": "eslint {src,tests}/**/*.{ts,tsx}", "lint:fix": "yarn lint --fix", "lint:types": "tsc --noEmit", "build:cjs": "tsc", diff --git a/src/useRafLoop.ts b/src/useRafLoop.ts index 9760ea6b7b..444d4ee14e 100644 --- a/src/useRafLoop.ts +++ b/src/useRafLoop.ts @@ -1,36 +1,45 @@ -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; -export type RafLoopReturns = [() => void, boolean, () => void]; +export type RafLoopReturns = [() => void, () => void, () => boolean]; -export default function useRafLoop(callback: CallableFunction): RafLoopReturns { +export default function useRafLoop(callback: FrameRequestCallback, initiallyActive = true): RafLoopReturns { const raf = useRef(null); - const [isActive, setIsActive] = useState(true); - - function loopStep(time: number) { - callback(time); - raf.current = requestAnimationFrame(loopStep); - } - - function loopStop() { - setIsActive(false); - } - - function loopStart() { - setIsActive(true); - } - - function clearCurrentLoop() { - raf.current && cancelAnimationFrame(raf.current); - } - - useEffect(() => clearCurrentLoop, []); + const rafActivity = useRef(false); + const rafCallback = useRef(callback); + rafCallback.current = callback; + + const step = useCallback((time: number) => { + if (rafActivity.current) { + rafCallback.current(time); + raf.current = requestAnimationFrame(step); + } + }, []); + + const result = useMemo(() => ([ + () => { // stop + if (rafActivity.current) { + rafActivity.current = false; + raf.current && cancelAnimationFrame(raf.current); + } + }, + () => { // start + if (!rafActivity.current) { + rafActivity.current = true; + raf.current = requestAnimationFrame(step); + } + }, + (): boolean => rafActivity.current // isActive + // eslint-disable-next-line react-hooks/exhaustive-deps + ] as RafLoopReturns), []); useEffect(() => { - clearCurrentLoop(); - isActive && (raf.current = requestAnimationFrame(loopStep)); + if (initiallyActive) { + result[1](); + } - return clearCurrentLoop; - }, [isActive, callback]); + return result[0]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return [loopStop, isActive, loopStart]; + return result; } diff --git a/stories/useRafLoop.story.tsx b/stories/useRafLoop.story.tsx index 076e35a5f1..0f860c8ae7 100644 --- a/stories/useRafLoop.story.tsx +++ b/stories/useRafLoop.story.tsx @@ -1,20 +1,27 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; -import { useRafLoop } from '../src'; +import { useRafLoop, useUpdate } from '../src'; import ShowDocs from './util/ShowDocs'; const Demo = () => { const [ticks, setTicks] = React.useState(0); + const [lastCall, setLastCall] = React.useState(0); + const update = useUpdate(); - const [loopStop, isActive, loopStart] = useRafLoop(() => { - setTicks(ticks + 1); + const [loopStop, loopStart, isActive] = useRafLoop((time) => { + setTicks(ticks => ticks + 1); + setLastCall(time); }); return (
RAF triggered: {ticks} (times)
+
Last high res timestamp: {lastCall}

- +
); }; diff --git a/tests/useRafLoop.test.tsx b/tests/useRafLoop.test.tsx index 36f7d4466f..e61e26835d 100644 --- a/tests/useRafLoop.test.tsx +++ b/tests/useRafLoop.test.tsx @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; import { replaceRaf } from 'raf-stub'; import useRafLoop from '../src/useRafLoop'; @@ -23,96 +23,129 @@ describe('useRafLoop', () => { expect(useRafLoop).toBeDefined(); }); - it('should return stop function, start function and loop state', () => { + it('should return object with start, stop and isActive functions', () => { const hook = renderHook(() => useRafLoop(() => false), { initialProps: false }); - expect(typeof hook.result.current[0]).toEqual('function'); - expect(typeof hook.result.current[1]).toEqual('boolean'); - expect(typeof hook.result.current[2]).toEqual('function'); + expect(hook.result.current).toStrictEqual([ + expect.any(Function), + expect.any(Function), + expect.any(Function), + ]); }); - it('should call a callback constantly inside the raf loop', () => { + it('should constantly call callback inside the raf loop', () => { const spy = jest.fn(); renderHook(() => useRafLoop(spy), { initialProps: false }); expect(spy).not.toBeCalled(); - requestAnimationFrame.step(); - requestAnimationFrame.step(); + requestAnimationFrame.step(2); expect(spy).toBeCalledTimes(2); + requestAnimationFrame.step(2); + expect(spy).toBeCalledTimes(4); }); - it('first element call should stop the loop', () => { + it('should not start the loop if 2nd hook parameter is falsy', () => { const spy = jest.fn(); - const hook = renderHook(() => useRafLoop(spy), { initialProps: false }); + renderHook(() => useRafLoop(spy, false), { initialProps: false }); expect(spy).not.toBeCalled(); - - act(() => { - hook.result.current[0](); - }); - requestAnimationFrame.step(); + requestAnimationFrame.step(2); expect(spy).not.toBeCalled(); }); - it('second element should represent loop state', () => { + it('should pass the time argument to given callback', () => { const spy = jest.fn(); - const hook = renderHook(() => useRafLoop(spy), { initialProps: false }); - - expect(hook.result.current[1]).toBe(true); + renderHook(() => useRafLoop(spy), { initialProps: false }); - // stop the loop - act(() => { - hook.result.current[0](); - }); - expect(hook.result.current[1]).toBe(false); + expect(spy).not.toBeCalled(); + requestAnimationFrame.step(); + expect(typeof spy.mock.calls[0][0]).toBe('number'); }); - it('third element call should restart loop', () => { + it('should stop the loop on component unmount', () => { const spy = jest.fn(); const hook = renderHook(() => useRafLoop(spy), { initialProps: false }); expect(spy).not.toBeCalled(); - // stop the loop - act(() => { - hook.result.current[0](); - }); - requestAnimationFrame.step(); - expect(spy).not.toBeCalled(); + requestAnimationFrame.step(2); + expect(spy).toBeCalledTimes(2); - // start the loop - act(() => { - hook.result.current[2](); - }); + hook.unmount(); - requestAnimationFrame.step(); - requestAnimationFrame.step(); + requestAnimationFrame.step(2); expect(spy).toBeCalledTimes(2); }); - it('loop should stop itself on unmount', () => { - const spy = jest.fn(); - const hook = renderHook(() => useRafLoop(spy), { initialProps: false }); + it('should call the actual callback when it changed', () => { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const hook = renderHook(({cb}) => useRafLoop(cb), { initialProps: {cb: spy1} }); - hook.unmount(); + expect(spy1).not.toBeCalled(); + requestAnimationFrame.step(2); + expect(spy1).toBeCalledTimes(2); - requestAnimationFrame.step(); + hook.rerender({cb: spy2}); - expect(spy).not.toBeCalled(); + requestAnimationFrame.step(2); + expect(spy1).toBeCalledTimes(2); + expect(spy2).toBeCalledTimes(2); }); - it('should pass timestamp as 1st argument of callback', () => { - const spy = jest.fn(); - const hook = renderHook(() => useRafLoop(spy), { initialProps: false }); + describe('returned methods', () => { + it('stop method should stop the loop', () => { + const spy = jest.fn(); + const hook = renderHook(() => useRafLoop(spy), { initialProps: false }); - requestAnimationFrame.step(); + const [stop] = hook.result.current; - act(() => { - hook.result.current[0](); + expect(spy).not.toBeCalled(); + requestAnimationFrame.step(2); + expect(spy).toBeCalledTimes(2); + + stop(); + + requestAnimationFrame.step(2); + expect(spy).toBeCalledTimes(2); }); - requestAnimationFrame.step(); + it('start method should start stopped loop', () => { + const spy = jest.fn(); + const hook = renderHook(() => useRafLoop(spy, false), { initialProps: false }); - expect(spy).toHaveBeenCalled(); - expect(typeof spy.mock.calls[0][0]).toBe('number'); + const [stop, start] = hook.result.current; + + expect(spy).not.toBeCalled(); + requestAnimationFrame.step(2); + expect(spy).not.toBeCalled(); + + start(); + + requestAnimationFrame.step(2); + expect(spy).toBeCalledTimes(2); + + stop(); + + requestAnimationFrame.step(2); + expect(spy).toBeCalledTimes(2); + + start(); + + requestAnimationFrame.step(2); + expect(spy).toBeCalledTimes(4); + }); + + it('isActive method should return current loop state', () => { + const spy = jest.fn(); + const hook = renderHook(() => useRafLoop(spy, false), { initialProps: false }); + + const [stop, start, isActive] = hook.result.current; + + expect(isActive()).toBe(false); + start(); + expect(isActive()).toBe(true); + stop(); + expect(isActive()).toBe(false); + }); }); }); From 46e01e09dcd120793690dd4c73db0461263e6fbf Mon Sep 17 00:00:00 2001 From: Steven Lundy Date: Wed, 1 Apr 2020 14:11:28 -0700 Subject: [PATCH 0070/1144] Fix error message typo --- src/util/createHTMLMediaHook.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/util/createHTMLMediaHook.ts b/src/util/createHTMLMediaHook.ts index 03403934b8..b72ea1f8dc 100644 --- a/src/util/createHTMLMediaHook.ts +++ b/src/util/createHTMLMediaHook.ts @@ -197,11 +197,19 @@ const createHTMLMediaHook = (tag: 'audio' | 'video') => { if (!el) { if (process.env.NODE_ENV !== 'production') { - console.error( - 'useAudio() ref to