@aweebit/react-essentials 0.6.0 → 0.7.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.
@@ -4,13 +4,21 @@
4
4
  * @license MIT
5
5
  * @copyright 2020 Julien CARON
6
6
  */
7
+ type UseEventListenerOverloadArgs<EventMap, K extends keyof EventMap, T extends EventTarget> = [
8
+ target: T | null,
9
+ eventName: K,
10
+ handler: (this: NoInfer<T>, event: EventMap[K]) => void,
11
+ options?: AddEventListenerOptions | boolean
12
+ ];
7
13
  /**
8
- * Adds `handler` as a listener for the event `eventName` of `element` with the
14
+ * Adds `handler` as a listener for the event `eventName` of `target` with the
9
15
  * provided `options` applied
10
16
  *
11
- * If `element` is `undefined`, `window` is used instead.
17
+ * If `target` is not provided, `window` is used instead.
12
18
  *
13
- * If `element` is `null`, no event listener is added.
19
+ * If `target` is `null`, no event listener is added. This is useful when
20
+ * working with DOM element refs, or when the event listener needs to be removed
21
+ * temporarily.
14
22
  *
15
23
  * @example
16
24
  * ```tsx
@@ -18,62 +26,49 @@
18
26
  * console.log(window.innerWidth, window.innerHeight);
19
27
  * });
20
28
  *
21
- * useEventListener(
22
- * 'visibilitychange',
23
- * () => console.log(document.visibilityState),
24
- * document
25
- * );
29
+ * useEventListener(document, 'visibilitychange', () => {
30
+ * console.log(document.visibilityState);
31
+ * });
26
32
  *
27
33
  * const buttonRef = useRef<HTMLButtonElement>(null);
28
- * useEventListener("click", () => console.log("click"), buttonRef.current);
34
+ * useEventListener(buttonRef.current, 'click', () => console.log('click'));
29
35
  * ```
30
36
  *
31
37
  * @ignore
32
38
  */
33
- export declare function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement>(eventName: K, handler: (this: NoInfer<T>, event: HTMLElementEventMap[K]) => void, element: T | null, options?: boolean | AddEventListenerOptions): void;
39
+ export declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (this: Window, event: WindowEventMap[K]) => void, options?: AddEventListenerOptions | boolean): void;
34
40
  /**
35
41
  * @see {@linkcode useEventListener}
36
42
  * @ignore
37
43
  */
38
- export declare function useEventListener<K extends keyof SVGElementEventMap, T extends SVGElement>(eventName: K, handler: (this: NoInfer<T>, event: SVGElementEventMap[K]) => void, element: T | null, options?: boolean | AddEventListenerOptions): void;
44
+ export declare function useEventListener<K extends keyof WindowEventMap>(...args: UseEventListenerOverloadArgs<WindowEventMap, K, Window>): void;
39
45
  /**
40
46
  * @see {@linkcode useEventListener}
41
47
  * @ignore
42
48
  */
43
- export declare function useEventListener<K extends keyof MathMLElementEventMap, T extends MathMLElement>(eventName: K, handler: (this: NoInfer<T>, event: MathMLElementEventMap[K]) => void, element: T | null, options?: boolean | AddEventListenerOptions): void;
49
+ export declare function useEventListener<K extends keyof DocumentEventMap>(...args: UseEventListenerOverloadArgs<DocumentEventMap, K, Document>): void;
44
50
  /**
45
51
  * @see {@linkcode useEventListener}
46
52
  * @ignore
47
53
  */
48
- export declare function useEventListener<K extends keyof DocumentEventMap>(eventName: K, handler: (this: Document, event: DocumentEventMap[K]) => void, element: Document, options?: boolean | AddEventListenerOptions): void;
54
+ export declare function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement>(...args: UseEventListenerOverloadArgs<HTMLElementEventMap, K, T>): void;
49
55
  /**
50
56
  * @see {@linkcode useEventListener}
51
57
  * @ignore
52
58
  */
53
- export declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (this: Window, event: WindowEventMap[K]) => void, element?: Window, options?: boolean | AddEventListenerOptions): void;
59
+ export declare function useEventListener<K extends keyof SVGElementEventMap, T extends SVGElement>(...args: UseEventListenerOverloadArgs<SVGElementEventMap, K, T>): void;
54
60
  /**
55
- * Adds `handler` as a listener for the event `eventName` of `element` with the
56
- * provided `options` applied
57
- *
58
- * If `element` is `undefined`, `window` is used instead.
59
- *
60
- * If `element` is `null`, no event listener is added.
61
- *
62
- * @example
63
- * ```tsx
64
- * useEventListener('resize', () => {
65
- * console.log(window.innerWidth, window.innerHeight);
66
- * });
67
- *
68
- * useEventListener(
69
- * 'visibilitychange',
70
- * () => console.log(document.visibilityState),
71
- * document
72
- * );
73
- *
74
- * const buttonRef = useRef<HTMLButtonElement>(null);
75
- * useEventListener("click", () => console.log("click"), buttonRef.current);
76
- * ```
61
+ * @see {@linkcode useEventListener}
62
+ * @ignore
63
+ */
64
+ export declare function useEventListener<K extends keyof MathMLElementEventMap, T extends MathMLElement>(...args: UseEventListenerOverloadArgs<MathMLElementEventMap, K, T>): void;
65
+ /**
66
+ * @see {@linkcode useEventListener}
67
+ */
68
+ export declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (this: Window, event: WindowEventMap[K]) => void, options?: AddEventListenerOptions | boolean): void;
69
+ /**
70
+ * @see {@linkcode useEventListener}
77
71
  */
78
- export declare function useEventListener<T extends EventTarget>(eventName: string, handler: (this: NoInfer<T>, event: Event) => void, element?: T | null, options?: boolean | AddEventListenerOptions): void;
72
+ export declare function useEventListener<T extends EventTarget>(target: T | null, eventName: string, handler: (this: NoInfer<T>, event: Event) => void, options?: AddEventListenerOptions | boolean): void;
73
+ export {};
79
74
  //# sourceMappingURL=useEventListener.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useEventListener.d.ts","sourceRoot":"","sources":["../../src/hooks/useEventListener.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,mBAAmB,EACnC,CAAC,SAAS,WAAW,EAErB,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,EAClE,OAAO,EAAE,CAAC,GAAG,IAAI,EACjB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,kBAAkB,EAClC,CAAC,SAAS,UAAU,EAEpB,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,IAAI,EACjE,OAAO,EAAE,CAAC,GAAG,IAAI,EACjB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,qBAAqB,EACrC,CAAC,SAAS,aAAa,EAEvB,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,EACpE,OAAO,EAAE,CAAC,GAAG,IAAI,EACjB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAC/D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7D,OAAO,EAAE,QAAQ,EACjB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EACzD,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,WAAW,EACpD,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,EACjD,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAClB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC"}
1
+ {"version":3,"file":"useEventListener.d.ts","sourceRoot":"","sources":["../../src/hooks/useEventListener.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,KAAK,4BAA4B,CAC/B,QAAQ,EACR,CAAC,SAAS,MAAM,QAAQ,EACxB,CAAC,SAAS,WAAW,IACnB;IACF,MAAM,EAAE,CAAC,GAAG,IAAI;IAChB,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI;IACvD,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO;CAC5C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EACzD,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,GAC1C,IAAI,CAAC;AAER;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7D,GAAG,IAAI,EAAE,4BAA4B,CAAC,cAAc,EAAE,CAAC,EAAE,MAAM,CAAC,GAC/D,IAAI,CAAC;AAER;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAC/D,GAAG,IAAI,EAAE,4BAA4B,CAAC,gBAAgB,EAAE,CAAC,EAAE,QAAQ,CAAC,GACnE,IAAI,CAAC;AAER;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,mBAAmB,EACnC,CAAC,SAAS,WAAW,EACrB,GAAG,IAAI,EAAE,4BAA4B,CAAC,mBAAmB,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,kBAAkB,EAClC,CAAC,SAAS,UAAU,EACpB,GAAG,IAAI,EAAE,4BAA4B,CAAC,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,qBAAqB,EACrC,CAAC,SAAS,aAAa,EACvB,GAAG,IAAI,EAAE,4BAA4B,CAAC,qBAAqB,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAE5E;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EACzD,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,GAC1C,IAAI,CAAC;AAER;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,WAAW,EACpD,MAAM,EAAE,CAAC,GAAG,IAAI,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,EACjD,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,GAC1C,IAAI,CAAC"}
@@ -5,7 +5,42 @@
5
5
  * @copyright 2020 Julien CARON
6
6
  */
7
7
  import { useEffect, useMemo, useRef } from 'react';
8
- export function useEventListener(eventName, handler, element, options) {
8
+ /**
9
+ * Adds `handler` as a listener for the event `eventName` of `target` with the
10
+ * provided `options` applied
11
+ *
12
+ * If `target` is not provided, `window` is used instead.
13
+ *
14
+ * If `target` is `null`, no event listener is added. This is useful when
15
+ * working with DOM element refs, or when the event listener needs to be removed
16
+ * temporarily.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * useEventListener('resize', () => {
21
+ * console.log(window.innerWidth, window.innerHeight);
22
+ * });
23
+ *
24
+ * useEventListener(document, 'visibilitychange', () => {
25
+ * console.log(document.visibilityState);
26
+ * });
27
+ *
28
+ * const buttonRef = useRef<HTMLButtonElement>(null);
29
+ * useEventListener(buttonRef.current, 'click', () => console.log('click'));
30
+ * ```
31
+ */
32
+ export function useEventListener(...args) {
33
+ let eventName;
34
+ let handler;
35
+ let target;
36
+ let options;
37
+ if (typeof args[0] === 'string') {
38
+ [eventName, handler, options] = args;
39
+ }
40
+ else {
41
+ [target, eventName, handler, options] =
42
+ args;
43
+ }
9
44
  const handlerRef = useRef(handler);
10
45
  handlerRef.current = handler;
11
46
  const { capture = false, once = false, passive, signal, } = typeof options === 'boolean' ? { capture: options } : (options ?? {});
@@ -13,21 +48,18 @@ export function useEventListener(eventName, handler, element, options) {
13
48
  // eslint-disable-next-line react-hooks/exhaustive-deps
14
49
  [capture, once, passive, signal]);
15
50
  useEffect(() => {
16
- if (element === null) {
51
+ if (target === null) {
17
52
  // No element has been attached to the ref yet
18
53
  return;
19
54
  }
20
- // Define the listening target
21
- const targetElement = element ?? window;
22
- // Create event listener that calls handler function stored in ref
55
+ const definedTarget = target ?? window;
23
56
  const listener = function (event) {
24
57
  handlerRef.current.call(this, event);
25
58
  };
26
- targetElement.addEventListener(eventName, listener, memoizedOptions);
27
- // Remove event listener on cleanup
59
+ definedTarget.addEventListener(eventName, listener, memoizedOptions);
28
60
  return () => {
29
- targetElement.removeEventListener(eventName, listener, memoizedOptions);
61
+ definedTarget.removeEventListener(eventName, listener, memoizedOptions);
30
62
  };
31
- }, [eventName, element, memoizedOptions]);
63
+ }, [eventName, target, memoizedOptions]);
32
64
  }
33
65
  //# sourceMappingURL=useEventListener.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useEventListener.js","sourceRoot":"","sources":["../../src/hooks/useEventListener.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAuHnD,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,OAAkD,EAClD,OAA4B,EAC5B,OAA2C;IAE3C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAE7B,MAAM,EACJ,OAAO,GAAG,KAAK,EACf,IAAI,GAAG,KAAK,EACZ,OAAO,EACP,MAAM,GACP,GAAG,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAE1E,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAG,EAAE,CAAC,OAAO;IACb,uDAAuD;IACvD,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CACjC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,8CAA8C;YAC9C,OAAO;QACT,CAAC;QAED,8BAA8B;QAC9B,MAAM,aAAa,GAAG,OAAO,IAAI,MAAM,CAAC;QAExC,kEAAkE;QAClE,MAAM,QAAQ,GAAmB,UAAU,KAAK;YAC9C,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC,CAAC;QAEF,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QAErE,mCAAmC;QACnC,OAAO,GAAG,EAAE;YACV,aAAa,CAAC,mBAAmB,CAAC,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QAC1E,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"useEventListener.js","sourceRoot":"","sources":["../../src/hooks/useEventListener.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AA2GnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAG,IAAwE;IAE3E,IAAI,SAAiB,CAAC;IACtB,IAAI,OAAkD,CAAC;IACvD,IAAI,MAAsC,CAAC;IAC3C,IAAI,OAAsD,CAAC;IAE3D,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChC,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,IAAyC,CAAC;IAC5E,CAAC;SAAM,CAAC;QACN,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC;YACnC,IAAsC,CAAC;IAC3C,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAE7B,MAAM,EACJ,OAAO,GAAG,KAAK,EACf,IAAI,GAAG,KAAK,EACZ,OAAO,EACP,MAAM,GACP,GAAG,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAE1E,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAG,EAAE,CAAC,OAAO;IACb,uDAAuD;IACvD,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CACjC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,8CAA8C;YAC9C,OAAO;QACT,CAAC;QAED,MAAM,aAAa,GAAG,MAAM,IAAI,MAAM,CAAC;QAEvC,MAAM,QAAQ,GAAmB,UAAU,KAAK;YAC9C,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC,CAAC;QAEF,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QAErE,OAAO,GAAG,EAAE;YACV,aAAa,CAAC,mBAAmB,CAAC,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QAC1E,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;AAC3C,CAAC"}
@@ -4,6 +4,55 @@
4
4
  * This hook is designed in the most general way possible in order to cover all
5
5
  * imaginable use cases.
6
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
+ *
7
56
  * @param callback An optional callback function to call during renders that
8
57
  * were triggered with `forceUpdate()`
9
58
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useForceUpdate.d.ts","sourceRoot":"","sources":["../../src/hooks/useForceUpdate.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,CAAC,CAU1E"}
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"}
@@ -6,6 +6,55 @@ import { useReducer, useRef } from 'react';
6
6
  * This hook is designed in the most general way possible in order to cover all
7
7
  * imaginable use cases.
8
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
+ *
9
58
  * @param callback An optional callback function to call during renders that
10
59
  * were triggered with `forceUpdate()`
11
60
  *
@@ -27,12 +76,12 @@ import { useReducer, useRef } from 'react';
27
76
  export function useForceUpdate(callback) {
28
77
  // It is very unlikely that the number of updates will exceed
29
78
  // Number.MAX_SAFE_INTEGER, but not impossible. That is why we use bigints.
30
- const [counter, forceUpdate] = useReducer((prev) => prev + 1n, 0n);
31
- const counterRef = useRef(counter);
32
- if (counter !== counterRef.current) {
33
- counterRef.current = counter;
79
+ const [updateCount, forceUpdate] = useReducer((prev) => prev + 1n, 0n);
80
+ const updateCountRef = useRef(updateCount);
81
+ if (updateCount !== updateCountRef.current) {
82
+ updateCountRef.current = updateCount;
34
83
  callback?.();
35
84
  }
36
- return [forceUpdate, counter];
85
+ return [forceUpdate, updateCount];
37
86
  }
38
87
  //# sourceMappingURL=useForceUpdate.js.map
@@ -1 +1 @@
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;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,6DAA6D;IAC7D,2EAA2E;IAC3E,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,OAAO,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;QACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;QAC7B,QAAQ,EAAE,EAAE,CAAC;IACf,CAAC;IACD,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAChC,CAAC"}
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"}
@@ -7,6 +7,9 @@ export type ActionDispatch<ActionArg extends AnyActionArg> = (...args: ActionArg
7
7
  * `useReducer` hook with an additional dependency array `deps` that resets the
8
8
  * state to `initialState` when dependencies change
9
9
  *
10
+ * For motivation and examples, see
11
+ * https://github.com/facebook/react/issues/33041.
12
+ *
10
13
  * ### On linter support
11
14
  *
12
15
  * The `react-hooks/exhaustive-deps` ESLint rule doesn't support hooks where
@@ -15,7 +18,7 @@ export type ActionDispatch<ActionArg extends AnyActionArg> = (...args: ActionArg
15
18
  * possible, we don't want to artificially change the parameter's position.
16
19
  * Therefore, there will be no warnings about missing dependencies.
17
20
  * Because of that, additional caution is advised!
18
- * Be sure to check no dependencies are missing from the `deps` array.
21
+ * Be sure to check that no dependencies are missing from the `deps` array.
19
22
  *
20
23
  * Related issue: {@link https://github.com/facebook/react/issues/25443}.
21
24
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useReducerWithDeps.d.ts","sourceRoot":"","sources":["../../src/hooks/useReducerWithDeps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAuB,MAAM,OAAO,CAAC;AAOjE,cAAc;AAEd,MAAM,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;AAEtC,cAAc;AACd,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,YAAY,IAAI,CAC3D,GAAG,IAAI,EAAE,SAAS,KACf,IAAI,CAAC;AAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,SAAS,YAAY,EAC1D,OAAO,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,EACxC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAC5C,IAAI,EAAE,cAAc,GACnB,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAYxB"}
1
+ {"version":3,"file":"useReducerWithDeps.d.ts","sourceRoot":"","sources":["../../src/hooks/useReducerWithDeps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAuB,MAAM,OAAO,CAAC;AAOjE,cAAc;AAEd,MAAM,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;AAEtC,cAAc;AACd,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,YAAY,IAAI,CAC3D,GAAG,IAAI,EAAE,SAAS,KACf,IAAI,CAAC;AAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,SAAS,YAAY,EAC1D,OAAO,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,EACxC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAC5C,IAAI,EAAE,cAAc,GACnB,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAYxB"}
@@ -4,6 +4,9 @@ import { useStateWithDeps } from './useStateWithDeps.js';
4
4
  * `useReducer` hook with an additional dependency array `deps` that resets the
5
5
  * state to `initialState` when dependencies change
6
6
  *
7
+ * For motivation and examples, see
8
+ * https://github.com/facebook/react/issues/33041.
9
+ *
7
10
  * ### On linter support
8
11
  *
9
12
  * The `react-hooks/exhaustive-deps` ESLint rule doesn't support hooks where
@@ -12,7 +15,7 @@ import { useStateWithDeps } from './useStateWithDeps.js';
12
15
  * possible, we don't want to artificially change the parameter's position.
13
16
  * Therefore, there will be no warnings about missing dependencies.
14
17
  * Because of that, additional caution is advised!
15
- * Be sure to check no dependencies are missing from the `deps` array.
18
+ * Be sure to check that no dependencies are missing from the `deps` array.
16
19
  *
17
20
  * Related issue: {@link https://github.com/facebook/react/issues/25443}.
18
21
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useReducerWithDeps.js","sourceRoot":"","sources":["../../src/hooks/useReducerWithDeps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAezD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAwC,EACxC,YAA4C,EAC5C,IAAoB;IAEpB,uDAAuD;IACvD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,gBAAgB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAE/D,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAEnC,MAAM,QAAQ,GAAG,WAAW,CAAC,SAAS,QAAQ,CAAC,GAAG,IAAO;QACvD,QAAQ,CAAC,CAAC,aAAa,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IAC1E,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kDAAkD;IAE1D,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"useReducerWithDeps.js","sourceRoot":"","sources":["../../src/hooks/useReducerWithDeps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAezD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAwC,EACxC,YAA4C,EAC5C,IAAoB;IAEpB,uDAAuD;IACvD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,gBAAgB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAE/D,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAEnC,MAAM,QAAQ,GAAG,WAAW,CAAC,SAAS,QAAQ,CAAC,GAAG,IAAO;QACvD,QAAQ,CAAC,CAAC,aAAa,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IAC1E,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kDAAkD;IAE1D,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC3B,CAAC"}
@@ -9,6 +9,41 @@ import { type DependencyList, type Dispatch, type SetStateAction } from 'react';
9
9
  * `useState` hook with an additional dependency array `deps` that resets the
10
10
  * state to `initialState` when dependencies change
11
11
  *
12
+ * For motivation and more examples, see
13
+ * https://github.com/facebook/react/issues/33041.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * type Activity = 'breakfast' | 'exercise' | 'swim' | 'board games' | 'dinner';
18
+ *
19
+ * const timeOfDayOptions = ['morning', 'afternoon', 'evening'] as const;
20
+ * type TimeOfDay = (typeof timeOfDayOptions)[number];
21
+ *
22
+ * const activityOptionsByTimeOfDay: {
23
+ * [K in TimeOfDay]: [Activity, ...Activity[]];
24
+ * } = {
25
+ * morning: ['breakfast', 'exercise', 'swim'],
26
+ * afternoon: ['exercise', 'swim', 'board games'],
27
+ * evening: ['board games', 'dinner'],
28
+ * };
29
+ *
30
+ * export function Example() {
31
+ * const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>('morning');
32
+ *
33
+ * const activityOptions = activityOptionsByTimeOfDay[timeOfDay];
34
+ * const [activity, setActivity] = useStateWithDeps<Activity>(
35
+ * (prev) => {
36
+ * // Make sure activity is always valid for the current timeOfDay value,
37
+ * // but also don't reset it unless necessary:
38
+ * return prev && activityOptions.includes(prev) ? prev : activityOptions[0];
39
+ * },
40
+ * [activityOptions],
41
+ * );
42
+ *
43
+ * return '...';
44
+ * }
45
+ * ```
46
+ *
12
47
  * @param initialState The value to which the state is set when the component is
13
48
  * mounted or dependencies change
14
49
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useStateWithDeps.d.ts","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAIf;;;;;;;;;;;;GAYG;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,CA4ClC"}
1
+ {"version":3,"file":"useStateWithDeps.d.ts","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAGL,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"}
@@ -11,6 +11,41 @@ import { useForceUpdate } from './useForceUpdate.js';
11
11
  * `useState` hook with an additional dependency array `deps` that resets the
12
12
  * state to `initialState` when dependencies change
13
13
  *
14
+ * For motivation and more examples, see
15
+ * https://github.com/facebook/react/issues/33041.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * type Activity = 'breakfast' | 'exercise' | 'swim' | 'board games' | 'dinner';
20
+ *
21
+ * const timeOfDayOptions = ['morning', 'afternoon', 'evening'] as const;
22
+ * type TimeOfDay = (typeof timeOfDayOptions)[number];
23
+ *
24
+ * const activityOptionsByTimeOfDay: {
25
+ * [K in TimeOfDay]: [Activity, ...Activity[]];
26
+ * } = {
27
+ * morning: ['breakfast', 'exercise', 'swim'],
28
+ * afternoon: ['exercise', 'swim', 'board games'],
29
+ * evening: ['board games', 'dinner'],
30
+ * };
31
+ *
32
+ * export function Example() {
33
+ * const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>('morning');
34
+ *
35
+ * const activityOptions = activityOptionsByTimeOfDay[timeOfDay];
36
+ * const [activity, setActivity] = useStateWithDeps<Activity>(
37
+ * (prev) => {
38
+ * // Make sure activity is always valid for the current timeOfDay value,
39
+ * // but also don't reset it unless necessary:
40
+ * return prev && activityOptions.includes(prev) ? prev : activityOptions[0];
41
+ * },
42
+ * [activityOptions],
43
+ * );
44
+ *
45
+ * return '...';
46
+ * }
47
+ * ```
48
+ *
14
49
  * @param initialState The value to which the state is set when the component is
15
50
  * mounted or dependencies change
16
51
  *
@@ -21,12 +56,10 @@ import { useForceUpdate } from './useForceUpdate.js';
21
56
  * @param deps Dependencies that reset the state to `initialState`
22
57
  */
23
58
  export function useStateWithDeps(initialState, deps) {
24
- // It would be possible to use useState instead of
25
- // useRef to store the state, however this would
26
- // trigger re-renders whenever the state is reset due
27
- // to a change in dependencies. In order to avoid these
28
- // re-renders, the state is stored in a ref and an
29
- // update is triggered via forceUpdate below when necessary
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.
30
63
  const state = useRef(undefined);
31
64
  const prevDeps = useRef(deps);
32
65
  const isMounted = useRef(false);
@@ -1 +1 @@
1
- {"version":3,"file":"useStateWithDeps.js","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,WAAW,EACX,MAAM,GAIP,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAA4C,EAC5C,IAAoB;IAEpB,kDAAkD;IAClD,gDAAgD;IAChD,qDAAqD;IACrD,uDAAuD;IACvD,kDAAkD;IAClD,2DAA2D;IAC3D,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,WAAW,CAAC,SAAS,WAAW,CAClD,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,EAAE,EAAE,CAAC,CAAC,CAAC,kDAAkD;IAE1D,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;;;;;GAKG;AAEH,OAAO,EACL,WAAW,EACX,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,WAAW,CAAC,SAAS,WAAW,CAClD,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,EAAE,EAAE,CAAC,CAAC,CAAC,kDAAkD;IAE1D,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;AACtC,CAAC"}
@@ -16,6 +16,8 @@ export type RestrictedContext<T> = Context<T> extends Provider<T> ? {
16
16
  displayName: string;
17
17
  };
18
18
  /**
19
+ * The return type of {@linkcode createSafeContext}
20
+ *
19
21
  * @see {@linkcode createSafeContext}
20
22
  */
21
23
  export type SafeContext<DisplayName extends string, T> = {
@@ -28,24 +30,55 @@ export type SafeContext<DisplayName extends string, T> = {
28
30
  * type and a hook that returns the current context value if one was provided,
29
31
  * or throws an error otherwise
30
32
  *
33
+ * The advantages over vanilla `createContext` are that no default value has to
34
+ * be provided, and that a meaningful context name is displayed in dev tools
35
+ * instead of generic `Context.Provider`.
36
+ *
31
37
  * @example
32
38
  * ```tsx
33
- * const { ItemsContext, useItems } = createSafeContext<string[]>()('Items');
39
+ * enum Direction {
40
+ * Up,
41
+ * Down,
42
+ * Left,
43
+ * Right,
44
+ * }
45
+ *
46
+ * // Before
47
+ * const DirectionContext = createContext<Direction | undefined>(undefined);
48
+ * DirectionContext.displayName = 'DirectionContext';
49
+ *
50
+ * const useDirection = () => {
51
+ * const direction = useContext(DirectionContext);
52
+ * if (direction === undefined) {
53
+ * // Called outside of a <DirectionContext.Provider> boundary!
54
+ * // Or maybe undefined was explicitly provided as the context value
55
+ * // (ideally that shouldn't be allowed, but it is because we had to include
56
+ * // undefined in the context type so as to provide a meaningful default)
57
+ * throw new Error('No DirectionContext value was provided');
58
+ * }
59
+ * // Thanks to the undefined check, the type is now narrowed down to Direction
60
+ * return direction;
61
+ * };
62
+ *
63
+ * // After
64
+ * const { DirectionContext, useDirection } =
65
+ * createSafeContext<Direction>()('Direction'); // That's it :)
34
66
  *
35
67
  * const Parent = () => (
36
- * <ItemsContext value={['compass', 'newspaper', 'banana']}>
68
+ * // Providing undefined as the value is not allowed 👍
69
+ * <Direction.Provider value={Direction.Up}>
37
70
  * <Child />
38
- * </ItemsContext>
71
+ * </Direction.Provider>
39
72
  * );
40
73
  *
41
- * const Child = () => useItems().join(', ');
74
+ * const Child = () => `Current direction: ${Direction[useDirection()]}`;
42
75
  * ```
43
76
  *
44
77
  * @returns
45
78
  * A function that accepts a single string argument `displayName` (e.g.
46
- * `"Items"`) and returns an object with the following properties:
47
- * - ``` `${displayName}Context` ``` (e.g. `ItemsContext`): the context
48
- * - ``` `use${displayName}` ``` (e.g. `useItems`): a hook that returns the
79
+ * `"Direction"`) and returns an object with the following properties:
80
+ * - ``` `${displayName}Context` ``` (e.g. `DirectionContext`): the context
81
+ * - ``` `use${displayName}` ``` (e.g. `useDirection`): a hook that returns the
49
82
  * current context value if one was provided, or throws an error otherwise
50
83
  */
51
84
  export declare function createSafeContext<T = never>(): <DisplayName extends string>(displayName: [T] extends [never] ? never : ArgumentFallback<DisplayName, never, string>) => SafeContext<DisplayName, T>;
@@ -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;;;;;;;GAOG;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;;GAEG;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;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;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;;;;;;;GAOG;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;;;;GAIG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;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"}