@o/hooks 0.16.19

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/cjs/index.cjs +32 -0
  4. package/dist/cjs/index.native.js +35 -0
  5. package/dist/cjs/index.native.js.map +1 -0
  6. package/dist/cjs/useClickOutside.cjs +52 -0
  7. package/dist/cjs/useClickOutside.native.js +63 -0
  8. package/dist/cjs/useClickOutside.native.js.map +1 -0
  9. package/dist/cjs/useDebouncePrepend.cjs +66 -0
  10. package/dist/cjs/useDebouncePrepend.native.js +70 -0
  11. package/dist/cjs/useDebouncePrepend.native.js.map +1 -0
  12. package/dist/cjs/useDeepMemoizedObject.cjs +207 -0
  13. package/dist/cjs/useDeepMemoizedObject.native.js +258 -0
  14. package/dist/cjs/useDeepMemoizedObject.native.js.map +1 -0
  15. package/dist/cjs/useDeepMemoizedObject.test.cjs +321 -0
  16. package/dist/cjs/useDeepMemoizedObject.test.native.js +324 -0
  17. package/dist/cjs/useDeepMemoizedObject.test.native.js.map +1 -0
  18. package/dist/cjs/useDeferredBoolean.cjs +39 -0
  19. package/dist/cjs/useDeferredBoolean.native.js +42 -0
  20. package/dist/cjs/useDeferredBoolean.native.js.map +1 -0
  21. package/dist/cjs/useEffectOnceGlobally.cjs +45 -0
  22. package/dist/cjs/useEffectOnceGlobally.native.js +50 -0
  23. package/dist/cjs/useEffectOnceGlobally.native.js.map +1 -0
  24. package/dist/cjs/useEvent.cjs +33 -0
  25. package/dist/cjs/useEvent.native.js +41 -0
  26. package/dist/cjs/useEvent.native.js.map +1 -0
  27. package/dist/cjs/useIsMounted.cjs +35 -0
  28. package/dist/cjs/useIsMounted.native.js +38 -0
  29. package/dist/cjs/useIsMounted.native.js.map +1 -0
  30. package/dist/cjs/useLastValue.cjs +31 -0
  31. package/dist/cjs/useLastValue.native.js +34 -0
  32. package/dist/cjs/useLastValue.native.js.map +1 -0
  33. package/dist/cjs/useLastValueIf.cjs +38 -0
  34. package/dist/cjs/useLastValueIf.native.js +42 -0
  35. package/dist/cjs/useLastValueIf.native.js.map +1 -0
  36. package/dist/cjs/useMemoStable.cjs +35 -0
  37. package/dist/cjs/useMemoStable.native.js +39 -0
  38. package/dist/cjs/useMemoStable.native.js.map +1 -0
  39. package/dist/cjs/useMemoizedObjectList.cjs +64 -0
  40. package/dist/cjs/useMemoizedObjectList.native.js +86 -0
  41. package/dist/cjs/useMemoizedObjectList.native.js.map +1 -0
  42. package/dist/cjs/useThrottle.cjs +52 -0
  43. package/dist/cjs/useThrottle.native.js +59 -0
  44. package/dist/cjs/useThrottle.native.js.map +1 -0
  45. package/dist/cjs/useWarnIfDepsChange.cjs +65 -0
  46. package/dist/cjs/useWarnIfDepsChange.native.js +68 -0
  47. package/dist/cjs/useWarnIfDepsChange.native.js.map +1 -0
  48. package/dist/cjs/useWarnIfMemoChangesOften.cjs +43 -0
  49. package/dist/cjs/useWarnIfMemoChangesOften.native.js +48 -0
  50. package/dist/cjs/useWarnIfMemoChangesOften.native.js.map +1 -0
  51. package/dist/esm/index.js +14 -0
  52. package/dist/esm/index.js.map +1 -0
  53. package/dist/esm/index.mjs +14 -0
  54. package/dist/esm/index.mjs.map +1 -0
  55. package/dist/esm/index.native.js +14 -0
  56. package/dist/esm/index.native.js.map +1 -0
  57. package/dist/esm/useClickOutside.mjs +27 -0
  58. package/dist/esm/useClickOutside.mjs.map +1 -0
  59. package/dist/esm/useClickOutside.native.js +35 -0
  60. package/dist/esm/useClickOutside.native.js.map +1 -0
  61. package/dist/esm/useDebouncePrepend.mjs +41 -0
  62. package/dist/esm/useDebouncePrepend.mjs.map +1 -0
  63. package/dist/esm/useDebouncePrepend.native.js +42 -0
  64. package/dist/esm/useDebouncePrepend.native.js.map +1 -0
  65. package/dist/esm/useDeepMemoizedObject.mjs +180 -0
  66. package/dist/esm/useDeepMemoizedObject.mjs.map +1 -0
  67. package/dist/esm/useDeepMemoizedObject.native.js +228 -0
  68. package/dist/esm/useDeepMemoizedObject.native.js.map +1 -0
  69. package/dist/esm/useDeepMemoizedObject.test.mjs +322 -0
  70. package/dist/esm/useDeepMemoizedObject.test.mjs.map +1 -0
  71. package/dist/esm/useDeepMemoizedObject.test.native.js +322 -0
  72. package/dist/esm/useDeepMemoizedObject.test.native.js.map +1 -0
  73. package/dist/esm/useDeferredBoolean.mjs +14 -0
  74. package/dist/esm/useDeferredBoolean.mjs.map +1 -0
  75. package/dist/esm/useDeferredBoolean.native.js +14 -0
  76. package/dist/esm/useDeferredBoolean.native.js.map +1 -0
  77. package/dist/esm/useEffectOnceGlobally.mjs +20 -0
  78. package/dist/esm/useEffectOnceGlobally.mjs.map +1 -0
  79. package/dist/esm/useEffectOnceGlobally.native.js +22 -0
  80. package/dist/esm/useEffectOnceGlobally.native.js.map +1 -0
  81. package/dist/esm/useEvent.mjs +8 -0
  82. package/dist/esm/useEvent.mjs.map +1 -0
  83. package/dist/esm/useEvent.native.js +13 -0
  84. package/dist/esm/useEvent.native.js.map +1 -0
  85. package/dist/esm/useIsMounted.mjs +10 -0
  86. package/dist/esm/useIsMounted.mjs.map +1 -0
  87. package/dist/esm/useIsMounted.native.js +10 -0
  88. package/dist/esm/useIsMounted.native.js.map +1 -0
  89. package/dist/esm/useLastValue.mjs +6 -0
  90. package/dist/esm/useLastValue.mjs.map +1 -0
  91. package/dist/esm/useLastValue.native.js +6 -0
  92. package/dist/esm/useLastValue.native.js.map +1 -0
  93. package/dist/esm/useLastValueIf.mjs +13 -0
  94. package/dist/esm/useLastValueIf.mjs.map +1 -0
  95. package/dist/esm/useLastValueIf.native.js +14 -0
  96. package/dist/esm/useLastValueIf.native.js.map +1 -0
  97. package/dist/esm/useMemoStable.mjs +10 -0
  98. package/dist/esm/useMemoStable.mjs.map +1 -0
  99. package/dist/esm/useMemoStable.native.js +11 -0
  100. package/dist/esm/useMemoStable.native.js.map +1 -0
  101. package/dist/esm/useMemoizedObjectList.mjs +39 -0
  102. package/dist/esm/useMemoizedObjectList.mjs.map +1 -0
  103. package/dist/esm/useMemoizedObjectList.native.js +58 -0
  104. package/dist/esm/useMemoizedObjectList.native.js.map +1 -0
  105. package/dist/esm/useThrottle.mjs +27 -0
  106. package/dist/esm/useThrottle.mjs.map +1 -0
  107. package/dist/esm/useThrottle.native.js +31 -0
  108. package/dist/esm/useThrottle.native.js.map +1 -0
  109. package/dist/esm/useWarnIfDepsChange.mjs +40 -0
  110. package/dist/esm/useWarnIfDepsChange.mjs.map +1 -0
  111. package/dist/esm/useWarnIfDepsChange.native.js +40 -0
  112. package/dist/esm/useWarnIfDepsChange.native.js.map +1 -0
  113. package/dist/esm/useWarnIfMemoChangesOften.mjs +18 -0
  114. package/dist/esm/useWarnIfMemoChangesOften.mjs.map +1 -0
  115. package/dist/esm/useWarnIfMemoChangesOften.native.js +20 -0
  116. package/dist/esm/useWarnIfMemoChangesOften.native.js.map +1 -0
  117. package/package.json +59 -0
  118. package/src/index.ts +13 -0
  119. package/src/useClickOutside.ts +34 -0
  120. package/src/useDebouncePrepend.ts +63 -0
  121. package/src/useDeepMemoizedObject.test.ts +344 -0
  122. package/src/useDeepMemoizedObject.ts +231 -0
  123. package/src/useDeferredBoolean.tsx +15 -0
  124. package/src/useEffectOnceGlobally.ts +41 -0
  125. package/src/useEvent.ts +7 -0
  126. package/src/useIsMounted.ts +11 -0
  127. package/src/useLastValue.ts +5 -0
  128. package/src/useLastValueIf.ts +15 -0
  129. package/src/useMemoStable.ts +25 -0
  130. package/src/useMemoizedObjectList.ts +74 -0
  131. package/src/useThrottle.ts +36 -0
  132. package/src/useWarnIfDepsChange.ts +61 -0
  133. package/src/useWarnIfMemoChangesOften.ts +24 -0
@@ -0,0 +1,74 @@
1
+ import { isEqualDeepLite } from '@o/helpers'
2
+ import { useEffect, useMemo, useRef } from 'react'
3
+
4
+ /**
5
+ * When zero mutates and inserts, it creates all new objects for everything, but this
6
+ * breaks react memoization, even when only the last item is mutated, or the last item
7
+ * is inserted. This hook should be considered temporary, I've brought up the idea with
8
+ * the zero team to implement this internally, as that would be significantly faster.
9
+ *
10
+ * for now, this basically will re-use the last message if JSON.stringify says its the same
11
+ * that way you skip a lot of re-rendering work when changing long lists of items
12
+ *
13
+ * NOTE: this leaks memory
14
+ */
15
+ export function useMemoizedObjectList<
16
+ Items extends readonly object[],
17
+ Item extends Items[0] = Items[0],
18
+ ItemKey extends keyof Item = keyof Item,
19
+ >(list: Items, identityKey: ItemKey): Items {
20
+ const memoizedItems = useRef<Record<string, any>>({})
21
+ const memoizedList = useRef<Items>(list)
22
+
23
+ const val = useMemo(() => {
24
+ let res = list
25
+ let didFindChange = false
26
+ const next: Item[] = []
27
+ const lastItems = memoizedItems.current
28
+
29
+ // changed size, always update array identity
30
+ if (list.length !== memoizedList.current.length) {
31
+ didFindChange = true
32
+ }
33
+
34
+ for (const item_ of list) {
35
+ const item = item_ as Item
36
+ const id = item[identityKey] as any
37
+ const last = lastItems[id]
38
+
39
+ // zero is returning a symbol on objects sometimes?
40
+ if (!last || !isEqualDeepLite(last, item)) {
41
+ didFindChange = true
42
+
43
+ // if (last) {
44
+ // console.info(`did change`, item, 'vs', last)
45
+ // for (const key in item) {
46
+ // if (!deepEqualLite(item[key], last[key])) {
47
+ // console.warn('changed', key)
48
+ // }
49
+ // }
50
+ // }
51
+
52
+ lastItems[id] = item
53
+ next.push(item)
54
+ } else {
55
+ next.push(last)
56
+ }
57
+ }
58
+
59
+ // if every item matches we can memoize the entire array
60
+ if (didFindChange) {
61
+ res = next as any
62
+ } else {
63
+ res = memoizedList.current
64
+ }
65
+
66
+ return res
67
+ }, [identityKey, list])
68
+
69
+ useEffect(() => {
70
+ memoizedList.current = val
71
+ }, [val])
72
+
73
+ return val
74
+ }
@@ -0,0 +1,36 @@
1
+ import { useRef } from 'react'
2
+
3
+ import { useEvent } from './useEvent'
4
+
5
+ type Timer = ReturnType<typeof setTimeout>
6
+
7
+ export const useThrottle = <T extends (...args: any[]) => any>(fn: T, delay = 100): T => {
8
+ const lastCallTime = useRef<number>(0)
9
+ const timeoutRef = useRef<Timer | null>(null)
10
+
11
+ const stableFn = useEvent(fn)
12
+
13
+ const throttledFn = useEvent((...args: Parameters<T>) => {
14
+ const now = Date.now()
15
+ const timeSinceLastCall = now - lastCallTime.current
16
+
17
+ if (timeSinceLastCall >= delay) {
18
+ // If enough time has passed, call immediately
19
+ lastCallTime.current = now
20
+ stableFn(...args)
21
+ } else {
22
+ // Otherwise, schedule a call for when the delay period is over
23
+ if (timeoutRef.current) {
24
+ clearTimeout(timeoutRef.current)
25
+ }
26
+
27
+ timeoutRef.current = setTimeout(() => {
28
+ lastCallTime.current = Date.now()
29
+ stableFn(...args)
30
+ timeoutRef.current = null
31
+ }, delay - timeSinceLastCall)
32
+ }
33
+ }) as T
34
+
35
+ return throttledFn
36
+ }
@@ -0,0 +1,61 @@
1
+ import { getCurrentComponentStack } from '@o/helpers'
2
+ import { useEffect, useId, useRef } from 'react'
3
+
4
+ export interface UseWarnIfDepsChangeOptions {
5
+ maxChanges?: number
6
+ ignoreIndexBefore?: number
7
+ name: string
8
+ }
9
+
10
+ export function useWarnIfDepsChange<T extends readonly unknown[]>(
11
+ deps: T,
12
+ options: UseWarnIfDepsChangeOptions
13
+ ): void {
14
+ const { maxChanges = 0, name, ignoreIndexBefore = 0 } = options
15
+
16
+ const changeCountRef = useRef(0)
17
+ const prevDepsRef = useRef<T | undefined>(undefined)
18
+ const id = useId()
19
+
20
+ useEffect(() => {
21
+ if (process.env.NODE_ENV === 'development') {
22
+ if (prevDepsRef.current !== undefined) {
23
+ const changedDeps: Array<{
24
+ index: number
25
+ prev: unknown
26
+ next: unknown
27
+ }> = []
28
+
29
+ const compareDeps = ignoreIndexBefore ? deps.slice(ignoreIndexBefore) : deps
30
+
31
+ compareDeps.forEach((dep, indexIn) => {
32
+ const index = indexIn + ignoreIndexBefore
33
+
34
+ if (prevDepsRef.current && prevDepsRef.current[index] !== dep) {
35
+ changedDeps.push({
36
+ index,
37
+ prev: prevDepsRef.current[index],
38
+ next: dep,
39
+ })
40
+ }
41
+ })
42
+
43
+ if (changedDeps.length > 0) {
44
+ changeCountRef.current++
45
+
46
+ if (changeCountRef.current > maxChanges) {
47
+ // don't use warn because it adds a huge stack
48
+ console.info(
49
+ `🔄 useWarnIfDepsChange "${name}" is changing too often! Changed ${changeCountRef.current} times (max: ${maxChanges})`,
50
+ changedDeps,
51
+ `\n id (${id}) at:`,
52
+ getCurrentComponentStack('short')
53
+ )
54
+ }
55
+ }
56
+ }
57
+
58
+ prevDepsRef.current = deps
59
+ }
60
+ })
61
+ }
@@ -0,0 +1,24 @@
1
+ import { emptyFn } from '@o/helpers'
2
+ import { useEffect, useRef } from 'react'
3
+
4
+ export const useWarnIfMemoChangesOften =
5
+ process.env.NODE_ENV === 'production'
6
+ ? (emptyFn as never)
7
+ : <T>(value: T, threshold = 5, name?: string) => {
8
+ const countRef = useRef(0)
9
+ const prevValueRef = useRef<T>(value)
10
+
11
+ useEffect(() => {
12
+ if (prevValueRef.current !== value) {
13
+ countRef.current++
14
+ prevValueRef.current = value
15
+
16
+ if (countRef.current > threshold) {
17
+ const warningName = name || 'Memoized value'
18
+ console.warn(
19
+ `🔄 ${warningName} is changing too often! Changed ${countRef.current} times (threshold: ${threshold})`
20
+ )
21
+ }
22
+ }
23
+ }, [value, threshold, name])
24
+ }