@aweebit/react-essentials 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/hooks/useDerivedState.d.ts +42 -0
- package/dist/hooks/useDerivedState.d.ts.map +1 -0
- package/dist/hooks/useDerivedState.js +92 -0
- package/dist/hooks/useEventListener.d.ts +38 -0
- package/dist/hooks/useEventListener.d.ts.map +1 -0
- package/dist/hooks/useEventListener.js +47 -0
- package/dist/hooks/useForceUpdate.d.ts +5 -0
- package/dist/hooks/useForceUpdate.d.ts.map +1 -0
- package/dist/hooks/useForceUpdate.js +5 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/lib/hooks/useDerivedState.ts +108 -0
- package/lib/hooks/useEventListener.ts +106 -0
- package/lib/hooks/useForceUpdate.ts +9 -0
- package/lib/index.ts +3 -0
- package/lib/utils/index.ts +16 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Wee Bit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Based on {@link https://github.com/peterjuras/use-state-with-deps}
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2020 Peter Juras
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
import { type DependencyList, type Dispatch, type SetStateAction } from 'react';
|
|
27
|
+
/**
|
|
28
|
+
* `useState` hook with an additional dependency array that resets
|
|
29
|
+
* the state to the `initialState` param when the dependencies passed
|
|
30
|
+
* in the `deps` array change.
|
|
31
|
+
*
|
|
32
|
+
* @param initialState
|
|
33
|
+
* The state that will be set when the component mounts or the
|
|
34
|
+
* dependencies change.
|
|
35
|
+
*
|
|
36
|
+
* It can also be a function which resolves to the state. If the state
|
|
37
|
+
* is reset due to a change of dependencies, this function will be called with the previous
|
|
38
|
+
* state (`undefined` for the first call upon mount).
|
|
39
|
+
* @param deps Dependencies for this hook that resets the state to `initialState`
|
|
40
|
+
*/
|
|
41
|
+
export default function useDerivedState<S>(initialState: S | ((previousState?: S) => S), deps: DependencyList): [S, Dispatch<SetStateAction<S>>];
|
|
42
|
+
//# sourceMappingURL=useDerivedState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDerivedState.d.ts","sourceRoot":"","sources":["../../lib/hooks/useDerivedState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAIf;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,CAAC,EACvC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAC5C,IAAI,EAAE,cAAc,GACnB,CAAC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAsDlC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Based on {@link https://github.com/peterjuras/use-state-with-deps}
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2020 Peter Juras
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
import { useCallback, useRef, } from 'react';
|
|
27
|
+
import { depsAreEqual, isFunction } from '../utils';
|
|
28
|
+
import useForceUpdate from './useForceUpdate';
|
|
29
|
+
/**
|
|
30
|
+
* `useState` hook with an additional dependency array that resets
|
|
31
|
+
* the state to the `initialState` param when the dependencies passed
|
|
32
|
+
* in the `deps` array change.
|
|
33
|
+
*
|
|
34
|
+
* @param initialState
|
|
35
|
+
* The state that will be set when the component mounts or the
|
|
36
|
+
* dependencies change.
|
|
37
|
+
*
|
|
38
|
+
* It can also be a function which resolves to the state. If the state
|
|
39
|
+
* is reset due to a change of dependencies, this function will be called with the previous
|
|
40
|
+
* state (`undefined` for the first call upon mount).
|
|
41
|
+
* @param deps Dependencies for this hook that resets the state to `initialState`
|
|
42
|
+
*/
|
|
43
|
+
export default function useDerivedState(initialState, deps) {
|
|
44
|
+
const isMounted = useRef(false);
|
|
45
|
+
// Determine initial state
|
|
46
|
+
let usableInitialState = null;
|
|
47
|
+
if (!isMounted.current) {
|
|
48
|
+
isMounted.current = true;
|
|
49
|
+
if (isFunction(initialState)) {
|
|
50
|
+
usableInitialState = initialState();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
usableInitialState = initialState;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// It would be possible to use useState instead of
|
|
57
|
+
// useRef to store the state, however this would
|
|
58
|
+
// trigger re-renders whenever the state is reset due
|
|
59
|
+
// to a change in dependencies. In order to avoid these
|
|
60
|
+
// re-renders, the state is stored in a ref and an
|
|
61
|
+
// update is triggered via forceUpdate below when necessary
|
|
62
|
+
const state = useRef(usableInitialState);
|
|
63
|
+
// Check if dependencies have changed
|
|
64
|
+
const prevDeps = useRef(deps);
|
|
65
|
+
if (!depsAreEqual(prevDeps.current, deps)) {
|
|
66
|
+
// Update state and deps
|
|
67
|
+
let nextState;
|
|
68
|
+
if (isFunction(initialState)) {
|
|
69
|
+
nextState = initialState(state.current);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
nextState = initialState;
|
|
73
|
+
}
|
|
74
|
+
state.current = nextState;
|
|
75
|
+
prevDeps.current = deps;
|
|
76
|
+
}
|
|
77
|
+
const [forceUpdate] = useForceUpdate();
|
|
78
|
+
const updateState = useCallback(function updateState(newState) {
|
|
79
|
+
let nextState;
|
|
80
|
+
if (isFunction(newState)) {
|
|
81
|
+
nextState = newState(state.current);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
nextState = newState;
|
|
85
|
+
}
|
|
86
|
+
if (!Object.is(state.current, nextState)) {
|
|
87
|
+
state.current = nextState;
|
|
88
|
+
forceUpdate();
|
|
89
|
+
}
|
|
90
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
91
|
+
return [state.current, updateState];
|
|
92
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Based on {@link https://github.com/juliencrn/usehooks-ts}
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2020 Julien CARON
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Adds `handler` as a listener for the event `eventName` of `element`
|
|
28
|
+
* (or `window` by default) with the provided `options` applied.
|
|
29
|
+
*
|
|
30
|
+
* It is the user's responsibility to make sure `element` and `options` values
|
|
31
|
+
* are correctly memoized!
|
|
32
|
+
*/
|
|
33
|
+
declare function useEventListener<K extends keyof SVGElementEventMap, T extends SVGElement>(eventName: K, handler: (event: SVGElementEventMap[K]) => void, element: T, options?: boolean | AddEventListenerOptions): void;
|
|
34
|
+
declare function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement>(eventName: K, handler: (event: HTMLElementEventMap[K]) => void, element: T, options?: boolean | AddEventListenerOptions): void;
|
|
35
|
+
declare function useEventListener<K extends keyof DocumentEventMap>(eventName: K, handler: (event: DocumentEventMap[K]) => void, element: Document, options?: boolean | AddEventListenerOptions): void;
|
|
36
|
+
declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (event: WindowEventMap[K]) => void, element?: Window, options?: boolean | AddEventListenerOptions): void;
|
|
37
|
+
export default useEventListener;
|
|
38
|
+
//# sourceMappingURL=useEventListener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEventListener.d.ts","sourceRoot":"","sources":["../../lib/hooks/useEventListener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH;;;;;;GAMG;AAGH,iBAAS,gBAAgB,CACvB,CAAC,SAAS,MAAM,kBAAkB,EAClC,CAAC,SAAS,UAAU,EAEpB,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC/C,OAAO,EAAE,CAAC,EACV,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAGR,iBAAS,gBAAgB,CACvB,CAAC,SAAS,MAAM,mBAAmB,EACnC,CAAC,SAAS,WAAW,EAErB,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,EAChD,OAAO,EAAE,CAAC,EACV,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAGR,iBAAS,gBAAgB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EACxD,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7C,OAAO,EAAE,QAAQ,EACjB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAGR,iBAAS,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EACtD,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3C,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAiCR,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Based on {@link https://github.com/juliencrn/usehooks-ts}
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2020 Julien CARON
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
27
|
+
function useEventListener(eventName, handler, element, options) {
|
|
28
|
+
// Create a ref that stores handler
|
|
29
|
+
const savedHandler = useRef(handler);
|
|
30
|
+
useLayoutEffect(() => {
|
|
31
|
+
savedHandler.current = handler;
|
|
32
|
+
}, [handler]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
// Define the listening target
|
|
35
|
+
const targetElement = element ?? window;
|
|
36
|
+
// Create event listener that calls handler function stored in ref
|
|
37
|
+
const listener = (event) => {
|
|
38
|
+
savedHandler.current(event);
|
|
39
|
+
};
|
|
40
|
+
targetElement.addEventListener(eventName, listener, options);
|
|
41
|
+
// Remove event listener on cleanup
|
|
42
|
+
return () => {
|
|
43
|
+
targetElement.removeEventListener(eventName, listener, options);
|
|
44
|
+
};
|
|
45
|
+
}, [eventName, element, options]);
|
|
46
|
+
}
|
|
47
|
+
export default useEventListener;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useForceUpdate.d.ts","sourceRoot":"","sources":["../../lib/hooks/useForceUpdate.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,UAAU,cAAc,IAAI;IACxC,MAAM,IAAI;IACV,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC;CAC3B,CAGA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAG5C,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,cAAc,GACnB,OAAO,CAKT"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
2
|
+
export function isFunction(input) {
|
|
3
|
+
return typeof input === 'function';
|
|
4
|
+
}
|
|
5
|
+
export function depsAreEqual(prevDeps, deps) {
|
|
6
|
+
return (prevDeps.length === deps.length &&
|
|
7
|
+
deps.every((dep, index) => Object.is(dep, prevDeps[index])));
|
|
8
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Based on {@link https://github.com/peterjuras/use-state-with-deps}
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2020 Peter Juras
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
useCallback,
|
|
29
|
+
useRef,
|
|
30
|
+
type DependencyList,
|
|
31
|
+
type Dispatch,
|
|
32
|
+
type SetStateAction,
|
|
33
|
+
} from 'react';
|
|
34
|
+
import { depsAreEqual, isFunction } from '../utils';
|
|
35
|
+
import useForceUpdate from './useForceUpdate';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `useState` hook with an additional dependency array that resets
|
|
39
|
+
* the state to the `initialState` param when the dependencies passed
|
|
40
|
+
* in the `deps` array change.
|
|
41
|
+
*
|
|
42
|
+
* @param initialState
|
|
43
|
+
* The state that will be set when the component mounts or the
|
|
44
|
+
* dependencies change.
|
|
45
|
+
*
|
|
46
|
+
* It can also be a function which resolves to the state. If the state
|
|
47
|
+
* is reset due to a change of dependencies, this function will be called with the previous
|
|
48
|
+
* state (`undefined` for the first call upon mount).
|
|
49
|
+
* @param deps Dependencies for this hook that resets the state to `initialState`
|
|
50
|
+
*/
|
|
51
|
+
export default function useDerivedState<S>(
|
|
52
|
+
initialState: S | ((previousState?: S) => S),
|
|
53
|
+
deps: DependencyList,
|
|
54
|
+
): [S, Dispatch<SetStateAction<S>>] {
|
|
55
|
+
const isMounted = useRef(false);
|
|
56
|
+
|
|
57
|
+
// Determine initial state
|
|
58
|
+
let usableInitialState: S | null = null;
|
|
59
|
+
if (!isMounted.current) {
|
|
60
|
+
isMounted.current = true;
|
|
61
|
+
if (isFunction(initialState)) {
|
|
62
|
+
usableInitialState = initialState();
|
|
63
|
+
} else {
|
|
64
|
+
usableInitialState = initialState;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// It would be possible to use useState instead of
|
|
69
|
+
// useRef to store the state, however this would
|
|
70
|
+
// trigger re-renders whenever the state is reset due
|
|
71
|
+
// to a change in dependencies. In order to avoid these
|
|
72
|
+
// re-renders, the state is stored in a ref and an
|
|
73
|
+
// update is triggered via forceUpdate below when necessary
|
|
74
|
+
const state = useRef(usableInitialState as S);
|
|
75
|
+
|
|
76
|
+
// Check if dependencies have changed
|
|
77
|
+
const prevDeps = useRef(deps);
|
|
78
|
+
if (!depsAreEqual(prevDeps.current, deps)) {
|
|
79
|
+
// Update state and deps
|
|
80
|
+
let nextState: S;
|
|
81
|
+
if (isFunction(initialState)) {
|
|
82
|
+
nextState = initialState(state.current);
|
|
83
|
+
} else {
|
|
84
|
+
nextState = initialState;
|
|
85
|
+
}
|
|
86
|
+
state.current = nextState;
|
|
87
|
+
prevDeps.current = deps;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const [forceUpdate] = useForceUpdate();
|
|
91
|
+
|
|
92
|
+
const updateState = useCallback(function updateState(
|
|
93
|
+
newState: S | ((previousState: S) => S),
|
|
94
|
+
): void {
|
|
95
|
+
let nextState: S;
|
|
96
|
+
if (isFunction(newState)) {
|
|
97
|
+
nextState = newState(state.current);
|
|
98
|
+
} else {
|
|
99
|
+
nextState = newState;
|
|
100
|
+
}
|
|
101
|
+
if (!Object.is(state.current, nextState)) {
|
|
102
|
+
state.current = nextState;
|
|
103
|
+
forceUpdate();
|
|
104
|
+
}
|
|
105
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
106
|
+
|
|
107
|
+
return [state.current, updateState];
|
|
108
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Based on {@link https://github.com/juliencrn/usehooks-ts}
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2020 Julien CARON
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Adds `handler` as a listener for the event `eventName` of `element`
|
|
31
|
+
* (or `window` by default) with the provided `options` applied.
|
|
32
|
+
*
|
|
33
|
+
* It is the user's responsibility to make sure `element` and `options` values
|
|
34
|
+
* are correctly memoized!
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// SVGElement Event based useEventListener interface
|
|
38
|
+
function useEventListener<
|
|
39
|
+
K extends keyof SVGElementEventMap,
|
|
40
|
+
T extends SVGElement,
|
|
41
|
+
>(
|
|
42
|
+
eventName: K,
|
|
43
|
+
handler: (event: SVGElementEventMap[K]) => void,
|
|
44
|
+
element: T,
|
|
45
|
+
options?: boolean | AddEventListenerOptions,
|
|
46
|
+
): void;
|
|
47
|
+
|
|
48
|
+
// HTMLElement Event based useEventListener interface
|
|
49
|
+
function useEventListener<
|
|
50
|
+
K extends keyof HTMLElementEventMap,
|
|
51
|
+
T extends HTMLElement,
|
|
52
|
+
>(
|
|
53
|
+
eventName: K,
|
|
54
|
+
handler: (event: HTMLElementEventMap[K]) => void,
|
|
55
|
+
element: T,
|
|
56
|
+
options?: boolean | AddEventListenerOptions,
|
|
57
|
+
): void;
|
|
58
|
+
|
|
59
|
+
// Document Event based useEventListener interface
|
|
60
|
+
function useEventListener<K extends keyof DocumentEventMap>(
|
|
61
|
+
eventName: K,
|
|
62
|
+
handler: (event: DocumentEventMap[K]) => void,
|
|
63
|
+
element: Document,
|
|
64
|
+
options?: boolean | AddEventListenerOptions,
|
|
65
|
+
): void;
|
|
66
|
+
|
|
67
|
+
// Window Event based useEventListener interface
|
|
68
|
+
function useEventListener<K extends keyof WindowEventMap>(
|
|
69
|
+
eventName: K,
|
|
70
|
+
handler: (event: WindowEventMap[K]) => void,
|
|
71
|
+
element?: Window,
|
|
72
|
+
options?: boolean | AddEventListenerOptions,
|
|
73
|
+
): void;
|
|
74
|
+
|
|
75
|
+
function useEventListener(
|
|
76
|
+
eventName: string,
|
|
77
|
+
handler: (event: Event) => void,
|
|
78
|
+
element?: EventTarget,
|
|
79
|
+
options?: boolean | AddEventListenerOptions,
|
|
80
|
+
) {
|
|
81
|
+
// Create a ref that stores handler
|
|
82
|
+
const savedHandler = useRef(handler);
|
|
83
|
+
|
|
84
|
+
useLayoutEffect(() => {
|
|
85
|
+
savedHandler.current = handler;
|
|
86
|
+
}, [handler]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
// Define the listening target
|
|
90
|
+
const targetElement = element ?? window;
|
|
91
|
+
|
|
92
|
+
// Create event listener that calls handler function stored in ref
|
|
93
|
+
const listener: typeof handler = (event) => {
|
|
94
|
+
savedHandler.current(event);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
targetElement.addEventListener(eventName, listener, options);
|
|
98
|
+
|
|
99
|
+
// Remove event listener on cleanup
|
|
100
|
+
return () => {
|
|
101
|
+
targetElement.removeEventListener(eventName, listener, options);
|
|
102
|
+
};
|
|
103
|
+
}, [eventName, element, options]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default useEventListener;
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DependencyList } from 'react';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
4
|
+
export function isFunction(input: unknown): input is Function {
|
|
5
|
+
return typeof input === 'function';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function depsAreEqual(
|
|
9
|
+
prevDeps: DependencyList,
|
|
10
|
+
deps: DependencyList,
|
|
11
|
+
): boolean {
|
|
12
|
+
return (
|
|
13
|
+
prevDeps.length === deps.length &&
|
|
14
|
+
deps.every((dep, index) => Object.is(dep, prevDeps[index]))
|
|
15
|
+
);
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aweebit/react-essentials",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"repository": "github:aweebit/react-essentials",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prettier:base": "prettier --write",
|
|
10
|
+
"prettier": "npm run prettier:base -- .",
|
|
11
|
+
"lint": "eslint --max-warnings=0",
|
|
12
|
+
"build": "rimraf dist && tsc -b -f",
|
|
13
|
+
"prepare": "husky && npm run build"
|
|
14
|
+
},
|
|
15
|
+
"lint-staged": {
|
|
16
|
+
"!(*.{jsx,jsx,ts,tsx})": "npm run prettier -- --ignore-unknown",
|
|
17
|
+
"**.{jsx,jsx,ts,tsx}": "npm run lint --"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": ">=18.0.0 <20"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@eslint/js": "^9.28.0",
|
|
24
|
+
"@types/react": "^19.1.6",
|
|
25
|
+
"eslint": "^9.28.0",
|
|
26
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
27
|
+
"husky": "^9.1.7",
|
|
28
|
+
"lint-staged": "^16.1.0",
|
|
29
|
+
"prettier": "3.5.3",
|
|
30
|
+
"rimraf": "^6.0.1",
|
|
31
|
+
"typescript": "~5.8.3",
|
|
32
|
+
"typescript-eslint": "^8.33.0"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|