@take-out/hooks 0.0.28

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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/cjs/index.cjs +30 -0
  4. package/dist/cjs/index.js +27 -0
  5. package/dist/cjs/index.js.map +6 -0
  6. package/dist/cjs/index.native.js +33 -0
  7. package/dist/cjs/index.native.js.map +1 -0
  8. package/dist/cjs/useClickOutside.cjs +43 -0
  9. package/dist/cjs/useClickOutside.js +37 -0
  10. package/dist/cjs/useClickOutside.js.map +6 -0
  11. package/dist/cjs/useClickOutside.native.js +51 -0
  12. package/dist/cjs/useClickOutside.native.js.map +1 -0
  13. package/dist/cjs/useDebouncePrepend.cjs +46 -0
  14. package/dist/cjs/useDebouncePrepend.js +40 -0
  15. package/dist/cjs/useDebouncePrepend.js.map +6 -0
  16. package/dist/cjs/useDebouncePrepend.native.js +54 -0
  17. package/dist/cjs/useDebouncePrepend.native.js.map +1 -0
  18. package/dist/cjs/useDeepMemoizedObject.cjs +148 -0
  19. package/dist/cjs/useDeepMemoizedObject.js +122 -0
  20. package/dist/cjs/useDeepMemoizedObject.js.map +6 -0
  21. package/dist/cjs/useDeepMemoizedObject.native.js +192 -0
  22. package/dist/cjs/useDeepMemoizedObject.native.js.map +1 -0
  23. package/dist/cjs/useDeepMemoizedObject.test.cjs +251 -0
  24. package/dist/cjs/useDeepMemoizedObject.test.js +187 -0
  25. package/dist/cjs/useDeepMemoizedObject.test.js.map +6 -0
  26. package/dist/cjs/useDeepMemoizedObject.test.native.js +261 -0
  27. package/dist/cjs/useDeepMemoizedObject.test.native.js.map +1 -0
  28. package/dist/cjs/useDeferredBoolean.cjs +34 -0
  29. package/dist/cjs/useDeferredBoolean.js +29 -0
  30. package/dist/cjs/useDeferredBoolean.js.map +6 -0
  31. package/dist/cjs/useDeferredBoolean.native.js +37 -0
  32. package/dist/cjs/useDeferredBoolean.native.js.map +1 -0
  33. package/dist/cjs/useEffectOnceGlobally.cjs +33 -0
  34. package/dist/cjs/useEffectOnceGlobally.js +28 -0
  35. package/dist/cjs/useEffectOnceGlobally.js.map +6 -0
  36. package/dist/cjs/useEffectOnceGlobally.native.js +38 -0
  37. package/dist/cjs/useEffectOnceGlobally.native.js.map +1 -0
  38. package/dist/cjs/useIsMounted.cjs +32 -0
  39. package/dist/cjs/useIsMounted.js +27 -0
  40. package/dist/cjs/useIsMounted.js.map +6 -0
  41. package/dist/cjs/useIsMounted.native.js +35 -0
  42. package/dist/cjs/useIsMounted.native.js.map +1 -0
  43. package/dist/cjs/useLastValue.cjs +29 -0
  44. package/dist/cjs/useLastValue.js +24 -0
  45. package/dist/cjs/useLastValue.js.map +6 -0
  46. package/dist/cjs/useLastValue.native.js +32 -0
  47. package/dist/cjs/useLastValue.native.js.map +1 -0
  48. package/dist/cjs/useLastValueIf.cjs +31 -0
  49. package/dist/cjs/useLastValueIf.js +25 -0
  50. package/dist/cjs/useLastValueIf.js.map +6 -0
  51. package/dist/cjs/useLastValueIf.native.js +35 -0
  52. package/dist/cjs/useLastValueIf.native.js.map +1 -0
  53. package/dist/cjs/useMemoStable.cjs +32 -0
  54. package/dist/cjs/useMemoStable.js +26 -0
  55. package/dist/cjs/useMemoStable.js.map +6 -0
  56. package/dist/cjs/useMemoStable.native.js +36 -0
  57. package/dist/cjs/useMemoStable.native.js.map +1 -0
  58. package/dist/cjs/useMemoizedObjectList.cjs +48 -0
  59. package/dist/cjs/useMemoizedObjectList.js +36 -0
  60. package/dist/cjs/useMemoizedObjectList.js.map +6 -0
  61. package/dist/cjs/useMemoizedObjectList.native.js +65 -0
  62. package/dist/cjs/useMemoizedObjectList.native.js.map +1 -0
  63. package/dist/cjs/useThrottle.cjs +39 -0
  64. package/dist/cjs/useThrottle.js +30 -0
  65. package/dist/cjs/useThrottle.js.map +6 -0
  66. package/dist/cjs/useThrottle.native.js +45 -0
  67. package/dist/cjs/useThrottle.native.js.map +1 -0
  68. package/dist/cjs/useWarnIfDepsChange.cjs +54 -0
  69. package/dist/cjs/useWarnIfDepsChange.js +46 -0
  70. package/dist/cjs/useWarnIfDepsChange.js.map +6 -0
  71. package/dist/cjs/useWarnIfDepsChange.native.js +58 -0
  72. package/dist/cjs/useWarnIfDepsChange.native.js.map +1 -0
  73. package/dist/cjs/useWarnIfMemoChangesOften.cjs +34 -0
  74. package/dist/cjs/useWarnIfMemoChangesOften.js +29 -0
  75. package/dist/cjs/useWarnIfMemoChangesOften.js.map +6 -0
  76. package/dist/cjs/useWarnIfMemoChangesOften.native.js +42 -0
  77. package/dist/cjs/useWarnIfMemoChangesOften.native.js.map +1 -0
  78. package/dist/esm/index.js +14 -0
  79. package/dist/esm/index.js.map +6 -0
  80. package/dist/esm/index.mjs +14 -0
  81. package/dist/esm/index.mjs.map +1 -0
  82. package/dist/esm/index.native.js +14 -0
  83. package/dist/esm/index.native.js.map +1 -0
  84. package/dist/esm/useClickOutside.js +22 -0
  85. package/dist/esm/useClickOutside.js.map +6 -0
  86. package/dist/esm/useClickOutside.mjs +20 -0
  87. package/dist/esm/useClickOutside.mjs.map +1 -0
  88. package/dist/esm/useClickOutside.native.js +25 -0
  89. package/dist/esm/useClickOutside.native.js.map +1 -0
  90. package/dist/esm/useDebouncePrepend.js +25 -0
  91. package/dist/esm/useDebouncePrepend.js.map +6 -0
  92. package/dist/esm/useDebouncePrepend.mjs +23 -0
  93. package/dist/esm/useDebouncePrepend.mjs.map +1 -0
  94. package/dist/esm/useDebouncePrepend.native.js +28 -0
  95. package/dist/esm/useDebouncePrepend.native.js.map +1 -0
  96. package/dist/esm/useDeepMemoizedObject.js +106 -0
  97. package/dist/esm/useDeepMemoizedObject.js.map +6 -0
  98. package/dist/esm/useDeepMemoizedObject.mjs +123 -0
  99. package/dist/esm/useDeepMemoizedObject.mjs.map +1 -0
  100. package/dist/esm/useDeepMemoizedObject.native.js +164 -0
  101. package/dist/esm/useDeepMemoizedObject.native.js.map +1 -0
  102. package/dist/esm/useDeepMemoizedObject.test.js +188 -0
  103. package/dist/esm/useDeepMemoizedObject.test.js.map +6 -0
  104. package/dist/esm/useDeepMemoizedObject.test.mjs +252 -0
  105. package/dist/esm/useDeepMemoizedObject.test.mjs.map +1 -0
  106. package/dist/esm/useDeepMemoizedObject.test.native.js +259 -0
  107. package/dist/esm/useDeepMemoizedObject.test.native.js.map +1 -0
  108. package/dist/esm/useDeferredBoolean.js +13 -0
  109. package/dist/esm/useDeferredBoolean.js.map +6 -0
  110. package/dist/esm/useDeferredBoolean.mjs +11 -0
  111. package/dist/esm/useDeferredBoolean.mjs.map +1 -0
  112. package/dist/esm/useDeferredBoolean.native.js +11 -0
  113. package/dist/esm/useDeferredBoolean.native.js.map +1 -0
  114. package/dist/esm/useEffectOnceGlobally.js +12 -0
  115. package/dist/esm/useEffectOnceGlobally.js.map +6 -0
  116. package/dist/esm/useEffectOnceGlobally.mjs +10 -0
  117. package/dist/esm/useEffectOnceGlobally.mjs.map +1 -0
  118. package/dist/esm/useEffectOnceGlobally.native.js +12 -0
  119. package/dist/esm/useEffectOnceGlobally.native.js.map +1 -0
  120. package/dist/esm/useIsMounted.js +11 -0
  121. package/dist/esm/useIsMounted.js.map +6 -0
  122. package/dist/esm/useIsMounted.mjs +9 -0
  123. package/dist/esm/useIsMounted.mjs.map +1 -0
  124. package/dist/esm/useIsMounted.native.js +9 -0
  125. package/dist/esm/useIsMounted.native.js.map +1 -0
  126. package/dist/esm/useLastValue.js +8 -0
  127. package/dist/esm/useLastValue.js.map +6 -0
  128. package/dist/esm/useLastValue.mjs +6 -0
  129. package/dist/esm/useLastValue.mjs.map +1 -0
  130. package/dist/esm/useLastValue.native.js +6 -0
  131. package/dist/esm/useLastValue.native.js.map +1 -0
  132. package/dist/esm/useLastValueIf.js +9 -0
  133. package/dist/esm/useLastValueIf.js.map +6 -0
  134. package/dist/esm/useLastValueIf.mjs +8 -0
  135. package/dist/esm/useLastValueIf.mjs.map +1 -0
  136. package/dist/esm/useLastValueIf.native.js +9 -0
  137. package/dist/esm/useLastValueIf.native.js.map +1 -0
  138. package/dist/esm/useMemoStable.js +11 -0
  139. package/dist/esm/useMemoStable.js.map +6 -0
  140. package/dist/esm/useMemoStable.mjs +9 -0
  141. package/dist/esm/useMemoStable.mjs.map +1 -0
  142. package/dist/esm/useMemoStable.native.js +10 -0
  143. package/dist/esm/useMemoStable.native.js.map +1 -0
  144. package/dist/esm/useMemoizedObjectList.js +21 -0
  145. package/dist/esm/useMemoizedObjectList.js.map +6 -0
  146. package/dist/esm/useMemoizedObjectList.mjs +25 -0
  147. package/dist/esm/useMemoizedObjectList.mjs.map +1 -0
  148. package/dist/esm/useMemoizedObjectList.native.js +39 -0
  149. package/dist/esm/useMemoizedObjectList.native.js.map +1 -0
  150. package/dist/esm/useThrottle.js +15 -0
  151. package/dist/esm/useThrottle.js.map +6 -0
  152. package/dist/esm/useThrottle.mjs +16 -0
  153. package/dist/esm/useThrottle.mjs.map +1 -0
  154. package/dist/esm/useThrottle.native.js +19 -0
  155. package/dist/esm/useThrottle.native.js.map +1 -0
  156. package/dist/esm/useWarnIfDepsChange.js +31 -0
  157. package/dist/esm/useWarnIfDepsChange.js.map +6 -0
  158. package/dist/esm/useWarnIfDepsChange.mjs +31 -0
  159. package/dist/esm/useWarnIfDepsChange.mjs.map +1 -0
  160. package/dist/esm/useWarnIfDepsChange.native.js +32 -0
  161. package/dist/esm/useWarnIfDepsChange.native.js.map +1 -0
  162. package/dist/esm/useWarnIfMemoChangesOften.js +14 -0
  163. package/dist/esm/useWarnIfMemoChangesOften.js.map +6 -0
  164. package/dist/esm/useWarnIfMemoChangesOften.mjs +11 -0
  165. package/dist/esm/useWarnIfMemoChangesOften.mjs.map +1 -0
  166. package/dist/esm/useWarnIfMemoChangesOften.native.js +16 -0
  167. package/dist/esm/useWarnIfMemoChangesOften.native.js.map +1 -0
  168. package/package.json +54 -0
  169. package/src/index.ts +13 -0
  170. package/src/useClickOutside.ts +34 -0
  171. package/src/useDebouncePrepend.ts +63 -0
  172. package/src/useDeepMemoizedObject.test.ts +343 -0
  173. package/src/useDeepMemoizedObject.ts +231 -0
  174. package/src/useDeferredBoolean.tsx +15 -0
  175. package/src/useEffectOnceGlobally.ts +41 -0
  176. package/src/useIsMounted.ts +11 -0
  177. package/src/useLastValue.ts +5 -0
  178. package/src/useLastValueIf.ts +15 -0
  179. package/src/useMemoStable.ts +24 -0
  180. package/src/useMemoizedObjectList.ts +74 -0
  181. package/src/useThrottle.ts +35 -0
  182. package/src/useWarnIfDepsChange.ts +61 -0
  183. package/src/useWarnIfMemoChangesOften.ts +24 -0
@@ -0,0 +1,41 @@
1
+ import { useEffect } from 'react'
2
+
3
+ const executedKeys = new Set<string>()
4
+
5
+ type WithId = { id: string }
6
+
7
+ type KeyType<T> = string | string[] | WithId | WithId[] | readonly WithId[] | undefined
8
+
9
+ /**
10
+ * Hook that ensures an effect only runs once globally across all component instances
11
+ * Uses a key (or array of keys) to determine if the effect has already been executed
12
+ */
13
+ export function useEffectOnceGlobally<T extends KeyType<T>>(
14
+ key: T,
15
+ callback: T extends undefined ? () => void : (value: NonNullable<T>) => void
16
+ ) {
17
+ const keyString = !key
18
+ ? undefined
19
+ : typeof key === 'string'
20
+ ? key
21
+ : Array.isArray(key)
22
+ ? typeof key[0] === 'string'
23
+ ? (key as string[]).sort().join('')
24
+ : (key as readonly WithId[])
25
+ .map((item) => item.id)
26
+ .sort()
27
+ .join('')
28
+ : (key as WithId).id
29
+
30
+ useEffect(() => {
31
+ if (!keyString || executedKeys.has(keyString)) {
32
+ return
33
+ }
34
+ executedKeys.add(keyString)
35
+ if (key !== undefined) {
36
+ ;(callback as (value: NonNullable<T>) => void)(key as NonNullable<T>)
37
+ } else {
38
+ ;(callback as () => void)()
39
+ }
40
+ }, [keyString, callback, key])
41
+ }
@@ -0,0 +1,11 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ export const useIsMounted = () => {
4
+ const [state, setState] = useState(false)
5
+
6
+ useEffect(() => {
7
+ setState(true)
8
+ }, [])
9
+
10
+ return state
11
+ }
@@ -0,0 +1,5 @@
1
+ import { useLastValueIf } from './useLastValueIf'
2
+
3
+ export function useLastValue<T>(value: T): T | undefined {
4
+ return useLastValueIf(value, true)
5
+ }
@@ -0,0 +1,15 @@
1
+ import { useRef } from 'react'
2
+
3
+ export function useLastValueIf<T>(value: T, keepLast = true): T | undefined {
4
+ // sorted [newest, older]
5
+ const lastTwoValuesRef = useRef<(T | undefined)[]>([])
6
+
7
+ const [latest] = lastTwoValuesRef.current
8
+ if (keepLast) {
9
+ if (latest !== value) {
10
+ lastTwoValuesRef.current = [value, latest]
11
+ }
12
+ }
13
+
14
+ return lastTwoValuesRef.current[1]
15
+ }
@@ -0,0 +1,24 @@
1
+ import { useMemo } from 'react'
2
+ import { useWarnIfDepsChange } from './useWarnIfDepsChange'
3
+
4
+ // create a useMemo that can log when it changes too often
5
+ // useful for development
6
+
7
+ export interface UseMemoStableOptions {
8
+ maxChanges?: number
9
+ ignoreIndexBefore?: number
10
+ name: string
11
+ }
12
+
13
+ export function useMemoStable<Value, T extends readonly unknown[]>(
14
+ getValue: () => Value,
15
+ deps: T,
16
+ options: UseMemoStableOptions = {
17
+ name: `(untitled)`,
18
+ }
19
+ ): Value {
20
+ useWarnIfDepsChange(deps, options)
21
+
22
+ // biome-ignore lint/correctness/useExhaustiveDependencies: ignore
23
+ return useMemo(getValue, deps)
24
+ }
@@ -0,0 +1,74 @@
1
+ import { useEffect, useMemo, useRef } from 'react'
2
+ import { isEqualDeepLite } from '@take-out/helpers'
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 = []
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,35 @@
1
+ import { useRef } from 'react'
2
+ import { useEvent } from 'tamagui'
3
+
4
+ type Timer = ReturnType<typeof setTimeout>
5
+
6
+ export const useThrottle = <T extends (...args: any[]) => any>(fn: T, delay = 100): T => {
7
+ const lastCallTime = useRef<number>(0)
8
+ const timeoutRef = useRef<Timer | null>(null)
9
+
10
+ const stableFn = useEvent(fn)
11
+
12
+ const throttledFn = useEvent((...args: Parameters<T>) => {
13
+ const now = Date.now()
14
+ const timeSinceLastCall = now - lastCallTime.current
15
+
16
+ if (timeSinceLastCall >= delay) {
17
+ // If enough time has passed, call immediately
18
+ lastCallTime.current = now
19
+ stableFn(...args)
20
+ } else {
21
+ // Otherwise, schedule a call for when the delay period is over
22
+ if (timeoutRef.current) {
23
+ clearTimeout(timeoutRef.current)
24
+ }
25
+
26
+ timeoutRef.current = setTimeout(() => {
27
+ lastCallTime.current = Date.now()
28
+ stableFn(...args)
29
+ timeoutRef.current = null
30
+ }, delay - timeSinceLastCall)
31
+ }
32
+ }) as T
33
+
34
+ return throttledFn
35
+ }
@@ -0,0 +1,61 @@
1
+ import { useEffect, useId, useRef } from 'react'
2
+ import { getCurrentComponentStack } from '@take-out/helpers'
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 { useEffect, useRef } from 'react'
2
+ import { emptyFn } from '@take-out/helpers'
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
+ }