@aweebit/react-essentials 0.8.0 → 0.9.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.
Files changed (36) hide show
  1. package/LICENSE +0 -2
  2. package/README.md +81 -185
  3. package/dist/hooks/index.d.ts +0 -1
  4. package/dist/hooks/index.d.ts.map +1 -1
  5. package/dist/hooks/index.js +0 -1
  6. package/dist/hooks/index.js.map +1 -1
  7. package/dist/hooks/useEventListener.d.ts +12 -11
  8. package/dist/hooks/useEventListener.d.ts.map +1 -1
  9. package/dist/hooks/useEventListener.js +3 -7
  10. package/dist/hooks/useEventListener.js.map +1 -1
  11. package/dist/hooks/useReducerWithDeps.d.ts +7 -0
  12. package/dist/hooks/useReducerWithDeps.d.ts.map +1 -1
  13. package/dist/hooks/useReducerWithDeps.js +10 -3
  14. package/dist/hooks/useReducerWithDeps.js.map +1 -1
  15. package/dist/hooks/useStateWithDeps.d.ts +6 -7
  16. package/dist/hooks/useStateWithDeps.d.ts.map +1 -1
  17. package/dist/hooks/useStateWithDeps.js +14 -45
  18. package/dist/hooks/useStateWithDeps.js.map +1 -1
  19. package/dist/misc/createSafeContext.d.ts +12 -12
  20. package/dist/misc/createSafeContext.d.ts.map +1 -1
  21. package/dist/utils.d.ts +0 -3
  22. package/dist/utils.d.ts.map +1 -1
  23. package/dist/utils.js +0 -4
  24. package/dist/utils.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/hooks/index.ts +0 -1
  27. package/src/hooks/useEventListener.ts +21 -17
  28. package/src/hooks/useReducerWithDeps.ts +10 -3
  29. package/src/hooks/useStateWithDeps.ts +14 -48
  30. package/src/misc/createSafeContext.ts +13 -13
  31. package/src/utils.ts +0 -8
  32. package/dist/hooks/useForceUpdate.d.ts +0 -75
  33. package/dist/hooks/useForceUpdate.d.ts.map +0 -1
  34. package/dist/hooks/useForceUpdate.js +0 -87
  35. package/dist/hooks/useForceUpdate.js.map +0 -1
  36. package/src/hooks/useForceUpdate.ts +0 -91
@@ -1 +1 @@
1
- {"version":3,"file":"useStateWithDeps.d.ts","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAIf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,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,CA0ClC"}
1
+ {"version":3,"file":"useStateWithDeps.d.ts","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAGf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,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,CAWlC"}
@@ -1,16 +1,14 @@
1
- /**
2
- * @file Based on {@link https://github.com/peterjuras/use-state-with-deps}
3
- *
4
- * @license MIT
5
- * @copyright 2020 Peter Juras
6
- */
7
- import { useRef, } from 'react';
8
- import { depsAreEqual, isFunction } from '../utils.js';
9
- import { useForceUpdate } from './useForceUpdate.js';
1
+ import { useState, } from 'react';
2
+ import { depsAreEqual } from '../utils.js';
10
3
  /**
11
4
  * `useState` hook with an additional dependency array `deps` that resets the
12
5
  * state to `initialState` when dependencies change
13
6
  *
7
+ * Due to React's limitations, a change in dependencies always causes two
8
+ * renders when using this hook. The result of the first render is thrown away
9
+ * as described in
10
+ * [useState > Storing information from previous renders](https://react.dev/reference/react/useState#storing-information-from-previous-renders).
11
+ *
14
12
  * For motivation and more examples, see
15
13
  * https://github.com/facebook/react/issues/33041.
16
14
  *
@@ -29,7 +27,7 @@ import { useForceUpdate } from './useForceUpdate.js';
29
27
  * evening: ['board games', 'dinner'],
30
28
  * };
31
29
  *
32
- * export function Example() {
30
+ * function Example() {
33
31
  * const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>('morning');
34
32
  *
35
33
  * const activityOptions = activityOptionsByTimeOfDay[timeOfDay];
@@ -56,41 +54,12 @@ import { useForceUpdate } from './useForceUpdate.js';
56
54
  * @param deps Dependencies that reset the state to `initialState`
57
55
  */
58
56
  export function useStateWithDeps(initialState, deps) {
59
- // It would be possible to use useState instead of useRef to store the state,
60
- // however this would trigger re-renders whenever the state is reset due to a
61
- // change in dependencies. In order to avoid these re-renders, the state is
62
- // stored in a ref, and updates are triggered with forceUpdate when necessary.
63
- const state = useRef(undefined);
64
- const prevDeps = useRef(deps);
65
- const isMounted = useRef(false);
66
- // If first render, or if dependencies have changed since last time
67
- if (!isMounted.current || !depsAreEqual(prevDeps.current, deps)) {
68
- // Update state and deps
69
- let nextState;
70
- if (isFunction(initialState)) {
71
- nextState = initialState(state.current);
72
- }
73
- else {
74
- nextState = initialState;
75
- }
76
- state.current = nextState;
77
- prevDeps.current = deps;
78
- isMounted.current = true;
57
+ const [state, setState] = useState(initialState);
58
+ const [prevDeps, setPrevDeps] = useState(deps);
59
+ if (!depsAreEqual(deps, prevDeps)) {
60
+ setPrevDeps(deps);
61
+ setState(initialState);
79
62
  }
80
- const [forceUpdate] = useForceUpdate();
81
- const updateState = useRef(function updateState(newState) {
82
- let nextState;
83
- if (isFunction(newState)) {
84
- nextState = newState(state.current);
85
- }
86
- else {
87
- nextState = newState;
88
- }
89
- if (!Object.is(state.current, nextState)) {
90
- state.current = nextState;
91
- forceUpdate();
92
- }
93
- }).current;
94
- return [state.current, updateState];
63
+ return [state, setState];
95
64
  }
96
65
  //# sourceMappingURL=useStateWithDeps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStateWithDeps.js","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,MAAM,GAIP,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAA4C,EAC5C,IAAoB;IAEpB,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,8EAA8E;IAC9E,MAAM,KAAK,GAAG,MAAM,CAAC,SAAc,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhC,mEAAmE;IACnE,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;QAChE,wBAAwB;QACxB,IAAI,SAAY,CAAC;QACjB,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7B,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,YAAY,CAAC;QAC3B,CAAC;QACD,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC;QAC1B,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,GAAG,cAAc,EAAE,CAAC;IAEvC,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,WAAW,CAC7C,QAAuC;QAEvC,IAAI,SAAY,CAAC;QACjB,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzB,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,QAAQ,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;YACzC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC;YAC1B,WAAW,EAAE,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC,OAAO,CAAC;IAEX,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;AACtC,CAAC"}
1
+ {"version":3,"file":"useStateWithDeps.js","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,GAIT,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAA4C,EAC5C,IAAoB;IAEpB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;IAEjD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE/C,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;QAClC,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,QAAQ,CAAC,YAAY,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC3B,CAAC"}
@@ -1,5 +1,17 @@
1
1
  import { type Context, type Provider } from 'react';
2
2
  import type { ArgumentFallback } from '../utils.js';
3
+ /**
4
+ * The return type of {@linkcode createSafeContext}
5
+ *
6
+ * @see
7
+ * {@linkcode createSafeContext},
8
+ * {@linkcode RestrictedContext}
9
+ */
10
+ export type SafeContext<DisplayName extends string, T> = {
11
+ [K in `${DisplayName}Context`]: RestrictedContext<T>;
12
+ } & {
13
+ [K in `use${DisplayName}`]: () => T;
14
+ };
3
15
  /**
4
16
  * A React context with a required `displayName` and the obsolete `Consumer`
5
17
  * property purposefully omitted so that it is impossible to pass the context
@@ -16,18 +28,6 @@ export type RestrictedContext<T> = Context<T> extends Provider<T> ? {
16
28
  Provider: Provider<T>;
17
29
  displayName: string;
18
30
  };
19
- /**
20
- * The return type of {@linkcode createSafeContext}
21
- *
22
- * @see
23
- * {@linkcode createSafeContext},
24
- * {@linkcode RestrictedContext}
25
- */
26
- export type SafeContext<DisplayName extends string, T> = {
27
- [K in `${DisplayName}Context`]: RestrictedContext<T>;
28
- } & {
29
- [K in `use${DisplayName}`]: () => T;
30
- };
31
31
  /**
32
32
  * For a given type `T`, returns a function that produces both a context of that
33
33
  * type and a hook that returns the current context value if one was provided,
@@ -1 +1 @@
1
- {"version":3,"file":"createSafeContext.d.ts","sourceRoot":"","sources":["../../src/misc/createSafeContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAA6B,MAAM,OAAO,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;;;;;;GAQG;AAIH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAC7B,OAAO,CAAC,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAC1B;IAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,GAC5D;IAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,CAAC,WAAW,SAAS,MAAM,EAAE,CAAC,IAAI;KACtD,CAAC,IAAI,GAAG,WAAW,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAC;CACrD,GAAG;KACD,CAAC,IAAI,MAAM,WAAW,EAAE,GAAG,MAAM,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,KAAK,MACjC,WAAW,SAAS,MAAM,EAChC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC5B,KAAK,GACL,gBAAgB,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,KAC/C,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,CAsB/B"}
1
+ {"version":3,"file":"createSafeContext.d.ts","sourceRoot":"","sources":["../../src/misc/createSafeContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAA6B,MAAM,OAAO,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,CAAC,WAAW,SAAS,MAAM,EAAE,CAAC,IAAI;KACtD,CAAC,IAAI,GAAG,WAAW,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAC;CACrD,GAAG;KACD,CAAC,IAAI,MAAM,WAAW,EAAE,GAAG,MAAM,CAAC;CACpC,CAAC;AAEF;;;;;;;;GAQG;AAIH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAC7B,OAAO,CAAC,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAC1B;IAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,GAC5D;IAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,KAAK,MACjC,WAAW,SAAS,MAAM,EAChC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC5B,KAAK,GACL,gBAAgB,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,KAC/C,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,CAsB/B"}
package/dist/utils.d.ts CHANGED
@@ -1,7 +1,4 @@
1
1
  import type { DependencyList } from 'react';
2
- export type Callable = (...args: never) => unknown;
3
2
  export type ArgumentFallback<T extends Base, Default extends Base, Base = unknown> = [T] extends [never] ? Default : [Base] extends [T] ? Default : T;
4
- export declare function noop(): void;
5
- export declare function isFunction(input: unknown): input is Callable;
6
3
  export declare function depsAreEqual(prevDeps: DependencyList, deps: DependencyList): boolean;
7
4
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC;AAEnD,MAAM,MAAM,gBAAgB,CAC1B,CAAC,SAAS,IAAI,EACd,OAAO,SAAS,IAAI,EACpB,IAAI,GAAG,OAAO,IACZ,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;AAErE,wBAAgB,IAAI,SAAK;AAEzB,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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,MAAM,MAAM,gBAAgB,CAC1B,CAAC,SAAS,IAAI,EACd,OAAO,SAAS,IAAI,EACpB,IAAI,GAAG,OAAO,IACZ,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;AAErE,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,cAAc,GACnB,OAAO,CAKT"}
package/dist/utils.js CHANGED
@@ -1,7 +1,3 @@
1
- export function noop() { }
2
- export function isFunction(input) {
3
- return typeof input === 'function';
4
- }
5
1
  export function depsAreEqual(prevDeps, deps) {
6
2
  return (prevDeps.length === deps.length &&
7
3
  deps.every((dep, index) => Object.is(dep, prevDeps[index])));
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,IAAI,KAAI,CAAC;AAEzB,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,QAAwB,EACxB,IAAoB;IAEpB,OAAO,CACL,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAC/B,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,YAAY,CAC1B,QAAwB,EACxB,IAAoB;IAEpB,OAAO,CACL,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAC/B,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aweebit/react-essentials",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "repository": "github:aweebit/react-essentials",
6
6
  "main": "dist/index.js",
@@ -1,4 +1,3 @@
1
1
  export * from './useEventListener.js';
2
- export * from './useForceUpdate.js';
3
2
  export * from './useReducerWithDeps.js';
4
3
  export * from './useStateWithDeps.js';
@@ -1,10 +1,3 @@
1
- /**
2
- * @file Based on {@link https://github.com/juliencrn/usehooks-ts}
3
- *
4
- * @license MIT
5
- * @copyright 2020 Julien CARON
6
- */
7
-
8
1
  import { useEffect, useMemo, useRef, type RefObject } from 'react';
9
2
 
10
3
  /**
@@ -13,27 +6,36 @@ import { useEffect, useMemo, useRef, type RefObject } from 'react';
13
6
  * @see
14
7
  * {@linkcode useEventListener},
15
8
  * {@linkcode UseEventListenerWithImplicitWindowTarget},
16
- * {@linkcode UseEventListenerWithExplicitTarget},
9
+ * {@linkcode UseEventListenerWithExplicitGlobalTarget},
17
10
  * {@linkcode UseEventListenerWithAnyExplicitTarget}
18
11
  */
19
12
  export type UseEventListener = UseEventListenerWithImplicitWindowTarget &
20
- UseEventListenerWithExplicitTarget<Window, WindowEventMap> &
21
- UseEventListenerWithExplicitTarget<Document, DocumentEventMap> &
22
- UseEventListenerWithExplicitTarget<HTMLElement, HTMLElementEventMap> &
23
- UseEventListenerWithExplicitTarget<SVGElement, SVGElementEventMap> &
24
- UseEventListenerWithExplicitTarget<MathMLElement, MathMLElementEventMap> &
13
+ UseEventListenerWithExplicitGlobalTarget &
25
14
  UseEventListenerWithAnyExplicitTarget;
26
15
 
27
16
  /**
28
17
  * @see
29
18
  * {@linkcode useEventListener},
30
- * {@linkcode UseEventListenerWithImplicitWindowTargetArgs} */
19
+ * {@linkcode UseEventListenerWithImplicitWindowTargetArgs}
20
+ */
31
21
  export type UseEventListenerWithImplicitWindowTarget = <
32
22
  K extends keyof WindowEventMap,
33
23
  >(
34
24
  ...args: UseEventListenerWithImplicitWindowTargetArgs<K>
35
25
  ) => void;
36
26
 
27
+ /**
28
+ * @see
29
+ * {@linkcode useEventListener},
30
+ * {@linkcode UseEventListenerWithExplicitTarget}
31
+ */
32
+ export type UseEventListenerWithExplicitGlobalTarget =
33
+ UseEventListenerWithExplicitTarget<Window, WindowEventMap> &
34
+ UseEventListenerWithExplicitTarget<Document, DocumentEventMap> &
35
+ UseEventListenerWithExplicitTarget<HTMLElement, HTMLElementEventMap> &
36
+ UseEventListenerWithExplicitTarget<SVGElement, SVGElementEventMap> &
37
+ UseEventListenerWithExplicitTarget<MathMLElement, MathMLElementEventMap>;
38
+
37
39
  /**
38
40
  * @see
39
41
  * {@linkcode useEventListener},
@@ -41,7 +43,7 @@ export type UseEventListenerWithImplicitWindowTarget = <
41
43
  */
42
44
  export type UseEventListenerWithExplicitTarget<
43
45
  Target extends EventTarget,
44
- EventMap = Record<string, Event>,
46
+ EventMap,
45
47
  > = <T extends Target, K extends keyof EventMap>(
46
48
  ...args: UseEventListenerWithExplicitTargetArgs<EventMap, T, K>
47
49
  ) => void;
@@ -52,7 +54,7 @@ export type UseEventListenerWithExplicitTarget<
52
54
  * {@linkcode UseEventListenerWithExplicitTarget}
53
55
  */
54
56
  export type UseEventListenerWithAnyExplicitTarget =
55
- UseEventListenerWithExplicitTarget<EventTarget>;
57
+ UseEventListenerWithExplicitTarget<EventTarget, Record<string, Event>>;
56
58
 
57
59
  /**
58
60
  * @see
@@ -149,7 +151,9 @@ export const useEventListener: UseEventListener = function useEventListener(
149
151
  target && !('addEventListener' in target) ? target.current : target;
150
152
 
151
153
  const handlerRef = useRef(handler);
152
- handlerRef.current = handler;
154
+ useEffect(() => {
155
+ handlerRef.current = handler;
156
+ }, [handler]);
153
157
 
154
158
  const {
155
159
  capture = false,
@@ -18,6 +18,13 @@ export type ActionDispatch<ActionArg extends AnyActionArg> = (
18
18
  * `useReducer` hook with an additional dependency array `deps` that resets the
19
19
  * state to `initialState` when dependencies change
20
20
  *
21
+ * This hook is the reducer pattern counterpart of {@linkcode useStateWithDeps}.
22
+ *
23
+ * Due to React's limitations, a change in dependencies always causes two
24
+ * renders when using this hook. The result of the first render is thrown away
25
+ * as described in
26
+ * [useState > Storing information from previous renders](https://react.dev/reference/react/useState#storing-information-from-previous-renders).
27
+ *
21
28
  * For motivation and examples, see
22
29
  * https://github.com/facebook/react/issues/33041.
23
30
  *
@@ -60,9 +67,9 @@ export function useReducerWithDeps<S, A extends AnyActionArg>(
60
67
  // Only the initially provided reducer is used
61
68
  const reducerRef = useRef(reducer);
62
69
 
63
- const dispatch = useRef(function dispatch(...args: A): void {
70
+ function dispatch(...args: A): void {
64
71
  setState((previousState) => reducerRef.current(previousState, ...args));
65
- }).current;
72
+ }
66
73
 
67
- return [state, dispatch];
74
+ return [state, useRef(dispatch).current];
68
75
  }
@@ -1,23 +1,20 @@
1
- /**
2
- * @file Based on {@link https://github.com/peterjuras/use-state-with-deps}
3
- *
4
- * @license MIT
5
- * @copyright 2020 Peter Juras
6
- */
7
-
8
1
  import {
9
- useRef,
2
+ useState,
10
3
  type DependencyList,
11
4
  type Dispatch,
12
5
  type SetStateAction,
13
6
  } from 'react';
14
- import { depsAreEqual, isFunction } from '../utils.js';
15
- import { useForceUpdate } from './useForceUpdate.js';
7
+ import { depsAreEqual } from '../utils.js';
16
8
 
17
9
  /**
18
10
  * `useState` hook with an additional dependency array `deps` that resets the
19
11
  * state to `initialState` when dependencies change
20
12
  *
13
+ * Due to React's limitations, a change in dependencies always causes two
14
+ * renders when using this hook. The result of the first render is thrown away
15
+ * as described in
16
+ * [useState > Storing information from previous renders](https://react.dev/reference/react/useState#storing-information-from-previous-renders).
17
+ *
21
18
  * For motivation and more examples, see
22
19
  * https://github.com/facebook/react/issues/33041.
23
20
  *
@@ -36,7 +33,7 @@ import { useForceUpdate } from './useForceUpdate.js';
36
33
  * evening: ['board games', 'dinner'],
37
34
  * };
38
35
  *
39
- * export function Example() {
36
+ * function Example() {
40
37
  * const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>('morning');
41
38
  *
42
39
  * const activityOptions = activityOptionsByTimeOfDay[timeOfDay];
@@ -66,45 +63,14 @@ export function useStateWithDeps<S>(
66
63
  initialState: S | ((previousState?: S) => S),
67
64
  deps: DependencyList,
68
65
  ): [S, Dispatch<SetStateAction<S>>] {
69
- // It would be possible to use useState instead of useRef to store the state,
70
- // however this would trigger re-renders whenever the state is reset due to a
71
- // change in dependencies. In order to avoid these re-renders, the state is
72
- // stored in a ref, and updates are triggered with forceUpdate when necessary.
73
- const state = useRef(undefined as S);
66
+ const [state, setState] = useState(initialState);
74
67
 
75
- const prevDeps = useRef(deps);
76
- const isMounted = useRef(false);
68
+ const [prevDeps, setPrevDeps] = useState(deps);
77
69
 
78
- // If first render, or if dependencies have changed since last time
79
- if (!isMounted.current || !depsAreEqual(prevDeps.current, deps)) {
80
- // Update state and deps
81
- let nextState: S;
82
- if (isFunction(initialState)) {
83
- nextState = initialState(state.current);
84
- } else {
85
- nextState = initialState;
86
- }
87
- state.current = nextState;
88
- prevDeps.current = deps;
89
- isMounted.current = true;
70
+ if (!depsAreEqual(deps, prevDeps)) {
71
+ setPrevDeps(deps);
72
+ setState(initialState);
90
73
  }
91
74
 
92
- const [forceUpdate] = useForceUpdate();
93
-
94
- const updateState = useRef(function updateState(
95
- newState: S | ((previousState: S) => S),
96
- ): void {
97
- let nextState: S;
98
- if (isFunction(newState)) {
99
- nextState = newState(state.current);
100
- } else {
101
- nextState = newState;
102
- }
103
- if (!Object.is(state.current, nextState)) {
104
- state.current = nextState;
105
- forceUpdate();
106
- }
107
- }).current;
108
-
109
- return [state.current, updateState];
75
+ return [state, setState];
110
76
  }
@@ -3,6 +3,19 @@ import type { ArgumentFallback } from '../utils.js';
3
3
 
4
4
  const moValueSymbol = Symbol('noValue');
5
5
 
6
+ /**
7
+ * The return type of {@linkcode createSafeContext}
8
+ *
9
+ * @see
10
+ * {@linkcode createSafeContext},
11
+ * {@linkcode RestrictedContext}
12
+ */
13
+ export type SafeContext<DisplayName extends string, T> = {
14
+ [K in `${DisplayName}Context`]: RestrictedContext<T>;
15
+ } & {
16
+ [K in `use${DisplayName}`]: () => T;
17
+ };
18
+
6
19
  /**
7
20
  * A React context with a required `displayName` and the obsolete `Consumer`
8
21
  * property purposefully omitted so that it is impossible to pass the context
@@ -20,19 +33,6 @@ export type RestrictedContext<T> =
20
33
  ? { Provider: Provider<T>; displayName: string } & Provider<T>
21
34
  : { Provider: Provider<T>; displayName: string };
22
35
 
23
- /**
24
- * The return type of {@linkcode createSafeContext}
25
- *
26
- * @see
27
- * {@linkcode createSafeContext},
28
- * {@linkcode RestrictedContext}
29
- */
30
- export type SafeContext<DisplayName extends string, T> = {
31
- [K in `${DisplayName}Context`]: RestrictedContext<T>;
32
- } & {
33
- [K in `use${DisplayName}`]: () => T;
34
- };
35
-
36
36
  /**
37
37
  * For a given type `T`, returns a function that produces both a context of that
38
38
  * type and a hook that returns the current context value if one was provided,
package/src/utils.ts CHANGED
@@ -1,19 +1,11 @@
1
1
  import type { DependencyList } from 'react';
2
2
 
3
- export type Callable = (...args: never) => unknown;
4
-
5
3
  export type ArgumentFallback<
6
4
  T extends Base,
7
5
  Default extends Base,
8
6
  Base = unknown,
9
7
  > = [T] extends [never] ? Default : [Base] extends [T] ? Default : T;
10
8
 
11
- export function noop() {}
12
-
13
- export function isFunction(input: unknown): input is Callable {
14
- return typeof input === 'function';
15
- }
16
-
17
9
  export function depsAreEqual(
18
10
  prevDeps: DependencyList,
19
11
  deps: DependencyList,
@@ -1,75 +0,0 @@
1
- /**
2
- * Enables you to imperatively trigger re-rendering of components
3
- *
4
- * This hook is designed in the most general way possible in order to cover all
5
- * imaginable use cases.
6
- *
7
- * @example
8
- * Sometimes, React's immutability constraints mean too much unnecessary copying
9
- * of data when new data arrives at a high frequency. In such cases, it might be
10
- * desirable to ignore the constraints by embracing imperative patterns.
11
- * Here is an example of a scenario where that can make sense:
12
- *
13
- * ```tsx
14
- * type SensorData = { timestamp: number; value: number };
15
- * const sensorDataRef = useRef<SensorData[]>([]);
16
- * const mostRecentSensorDataTimestampRef = useRef<number>(0);
17
- *
18
- * const [forceUpdate, updateCount] = useForceUpdate();
19
- * // Limiting the frequency of forced re-renders with some throttle function:
20
- * const throttledForceUpdateRef = useRef(throttle(forceUpdate));
21
- *
22
- * useEffect(() => {
23
- * return sensorDataObservable.subscribe((data: SensorData) => {
24
- * // Imagine new sensor data arrives every 1 millisecond. If we were following
25
- * // React's immutability rules by creating a new array every time, the data
26
- * // that's already there would have to be copied many times before the new
27
- * // data would even get a chance to be reflected in the UI for the first time
28
- * // because it typically takes much longer than 1 millisecond for a new frame
29
- * // to be displayed. To prevent the waste of computational resources, we just
30
- * // mutate the existing array every time instead:
31
- * sensorDataRef.current.push(data);
32
- * if (data.timestamp > mostRecentSensorDataTimestampRef.current) {
33
- * mostRecentSensorDataTimestampRef.current = data.timestamp;
34
- * }
35
- * throttledForceUpdateRef.current();
36
- * });
37
- * }, []);
38
- *
39
- * const [timeWindow, setTimeWindow] = useState(1000);
40
- * const selectedSensorData = useMemo(
41
- * () => {
42
- * // Keep this line if you don't want to disable the
43
- * // react-hooks/exhaustive-deps ESLint rule:
44
- * updateCount;
45
- * const threshold = mostRecentSensorDataTimestampRef.current - timeWindow;
46
- * return sensorDataRef.current.filter(
47
- * ({ timestamp }) => timestamp >= threshold,
48
- * );
49
- * },
50
- * // sensorDataRef.current always references the same array, so listing it as a
51
- * // dependency is pointless. Instead, updateCount should be used:
52
- * [updateCount, timeWindow],
53
- * );
54
- * ```
55
- *
56
- * @param callback An optional callback function to call during renders that
57
- * were triggered with `forceUpdate()`
58
- *
59
- * Can be used for conditionally calling state setters when state needs to be
60
- * reset. That is legal and better than using effects (see
61
- * {@link https://react.dev/learn/-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes You Might Not Need an Effect > Adjusting some state when a prop changes}),
62
- * but can often be avoided by using {@linkcode useStateWithDeps} or
63
- * {@linkcode useReducerWithDeps}.
64
- *
65
- * Important: the callback function is called once per render, not once per
66
- * `forceUpdate` call! If React batches `forceUpdate` calls, then it will only
67
- * be called once.
68
- *
69
- * @returns An array with the following two elements:
70
- *
71
- * 1. A `forceUpdate` function that triggers a re-render
72
- * 2. The number of times `forceUpdate` has been called so far
73
- */
74
- export declare function useForceUpdate(callback?: () => void): [() => void, bigint];
75
- //# sourceMappingURL=useForceUpdate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useForceUpdate.d.ts","sourceRoot":"","sources":["../../src/hooks/useForceUpdate.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,CAAC,CAU1E"}
@@ -1,87 +0,0 @@
1
- import { useReducer, useRef } from 'react';
2
- /* eslint-enable */
3
- /**
4
- * Enables you to imperatively trigger re-rendering of components
5
- *
6
- * This hook is designed in the most general way possible in order to cover all
7
- * imaginable use cases.
8
- *
9
- * @example
10
- * Sometimes, React's immutability constraints mean too much unnecessary copying
11
- * of data when new data arrives at a high frequency. In such cases, it might be
12
- * desirable to ignore the constraints by embracing imperative patterns.
13
- * Here is an example of a scenario where that can make sense:
14
- *
15
- * ```tsx
16
- * type SensorData = { timestamp: number; value: number };
17
- * const sensorDataRef = useRef<SensorData[]>([]);
18
- * const mostRecentSensorDataTimestampRef = useRef<number>(0);
19
- *
20
- * const [forceUpdate, updateCount] = useForceUpdate();
21
- * // Limiting the frequency of forced re-renders with some throttle function:
22
- * const throttledForceUpdateRef = useRef(throttle(forceUpdate));
23
- *
24
- * useEffect(() => {
25
- * return sensorDataObservable.subscribe((data: SensorData) => {
26
- * // Imagine new sensor data arrives every 1 millisecond. If we were following
27
- * // React's immutability rules by creating a new array every time, the data
28
- * // that's already there would have to be copied many times before the new
29
- * // data would even get a chance to be reflected in the UI for the first time
30
- * // because it typically takes much longer than 1 millisecond for a new frame
31
- * // to be displayed. To prevent the waste of computational resources, we just
32
- * // mutate the existing array every time instead:
33
- * sensorDataRef.current.push(data);
34
- * if (data.timestamp > mostRecentSensorDataTimestampRef.current) {
35
- * mostRecentSensorDataTimestampRef.current = data.timestamp;
36
- * }
37
- * throttledForceUpdateRef.current();
38
- * });
39
- * }, []);
40
- *
41
- * const [timeWindow, setTimeWindow] = useState(1000);
42
- * const selectedSensorData = useMemo(
43
- * () => {
44
- * // Keep this line if you don't want to disable the
45
- * // react-hooks/exhaustive-deps ESLint rule:
46
- * updateCount;
47
- * const threshold = mostRecentSensorDataTimestampRef.current - timeWindow;
48
- * return sensorDataRef.current.filter(
49
- * ({ timestamp }) => timestamp >= threshold,
50
- * );
51
- * },
52
- * // sensorDataRef.current always references the same array, so listing it as a
53
- * // dependency is pointless. Instead, updateCount should be used:
54
- * [updateCount, timeWindow],
55
- * );
56
- * ```
57
- *
58
- * @param callback An optional callback function to call during renders that
59
- * were triggered with `forceUpdate()`
60
- *
61
- * Can be used for conditionally calling state setters when state needs to be
62
- * reset. That is legal and better than using effects (see
63
- * {@link https://react.dev/learn/-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes You Might Not Need an Effect > Adjusting some state when a prop changes}),
64
- * but can often be avoided by using {@linkcode useStateWithDeps} or
65
- * {@linkcode useReducerWithDeps}.
66
- *
67
- * Important: the callback function is called once per render, not once per
68
- * `forceUpdate` call! If React batches `forceUpdate` calls, then it will only
69
- * be called once.
70
- *
71
- * @returns An array with the following two elements:
72
- *
73
- * 1. A `forceUpdate` function that triggers a re-render
74
- * 2. The number of times `forceUpdate` has been called so far
75
- */
76
- export function useForceUpdate(callback) {
77
- // It is very unlikely that the number of updates will exceed
78
- // Number.MAX_SAFE_INTEGER, but not impossible. That is why we use bigints.
79
- const [updateCount, forceUpdate] = useReducer((prev) => prev + 1n, 0n);
80
- const updateCountRef = useRef(updateCount);
81
- if (updateCount !== updateCountRef.current) {
82
- updateCountRef.current = updateCount;
83
- callback?.();
84
- }
85
- return [forceUpdate, updateCount];
86
- }
87
- //# sourceMappingURL=useForceUpdate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useForceUpdate.js","sourceRoot":"","sources":["../../src/hooks/useForceUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAK3C,mBAAmB;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,6DAA6D;IAC7D,2EAA2E;IAC3E,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;IACvE,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,WAAW,KAAK,cAAc,CAAC,OAAO,EAAE,CAAC;QAC3C,cAAc,CAAC,OAAO,GAAG,WAAW,CAAC;QACrC,QAAQ,EAAE,EAAE,CAAC;IACf,CAAC;IACD,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AACpC,CAAC"}