@codeleap/hooks 5.8.21 → 6.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -9
- package/package.json.bak +2 -2
- package/src/index.ts +7 -2
- package/src/onMount.ts +9 -0
- package/src/onUpdate.ts +8 -0
- package/src/useAnimatedState.ts +42 -0
- package/src/useBooleanToggle.ts +8 -0
- package/src/useComponentTestId.ts +13 -6
- package/src/useConditionalState.ts +12 -1
- package/src/useDebounce.ts +7 -0
- package/src/useDebounceCallback.ts +47 -0
- package/src/useDerivedRef.ts +21 -0
- package/src/useDerivedState.ts +42 -0
- package/src/useEffectOnce.ts +9 -0
- package/src/useFilteredList.ts +21 -0
- package/src/useForceRender.ts +7 -0
- package/src/useId.ts +8 -1
- package/src/useInterval.ts +44 -10
- package/src/useIsMounted.ts +12 -2
- package/src/useLazyStore.ts +18 -0
- package/src/useModal.ts +11 -1
- package/src/useOptions.ts +27 -0
- package/src/usePartialState.ts +8 -0
- package/src/usePlaces.ts +18 -0
- package/src/usePlacesAutocompleteUtils.ts +16 -0
- package/src/usePrevious.ts +11 -1
- package/src/usePromise.ts +16 -2
- package/src/useSearch/index.ts +16 -0
- package/src/useToggle.ts +8 -0
- package/src/useUncontrolled.ts +12 -0
- package/src/useUnmount.ts +10 -0
- package/src/useCounter.ts +0 -5
- package/src/useListState.ts +0 -92
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeleap/hooks",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.2",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -9,22 +9,22 @@
|
|
|
9
9
|
"directory": "packages/hooks"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@codeleap/config": "
|
|
13
|
-
"@codeleap/types": "
|
|
14
|
-
"@codeleap/utils": "
|
|
15
|
-
"@codeleap/logger": "
|
|
12
|
+
"@codeleap/config": "6.1.2",
|
|
13
|
+
"@codeleap/types": "6.1.2",
|
|
14
|
+
"@codeleap/utils": "6.1.2",
|
|
15
|
+
"@codeleap/logger": "6.1.2",
|
|
16
16
|
"ts-node-dev": "1.1.8"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "echo 'No build needed'"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@codeleap/types": "
|
|
23
|
-
"@codeleap/utils": "
|
|
24
|
-
"@codeleap/logger": "
|
|
22
|
+
"@codeleap/types": "6.1.2",
|
|
23
|
+
"@codeleap/utils": "6.1.2",
|
|
24
|
+
"@codeleap/logger": "6.1.2",
|
|
25
25
|
"axios": "^1.7.9",
|
|
26
26
|
"typescript": "5.5.2",
|
|
27
|
-
"react": "
|
|
27
|
+
"react": "19.1.0",
|
|
28
28
|
"@tanstack/react-query": "5.89.0"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/package.json.bak
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeleap/hooks",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.2",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@codeleap/logger": "workspace:*",
|
|
25
25
|
"axios": "^1.7.9",
|
|
26
26
|
"typescript": "5.5.2",
|
|
27
|
-
"react": "
|
|
27
|
+
"react": "19.1.0",
|
|
28
28
|
"@tanstack/react-query": "5.89.0"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,9 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
export * from './useConditionalState'
|
|
15
15
|
export * from './usePromise'
|
|
16
|
-
export * from './useListState'
|
|
17
16
|
export * from './useUncontrolled'
|
|
18
|
-
export * from './useCounter'
|
|
19
17
|
export * from './useForceRender'
|
|
20
18
|
export * from './useDebounce'
|
|
21
19
|
export * from './useInterval'
|
|
@@ -34,6 +32,13 @@ export * from './usePartialState'
|
|
|
34
32
|
export * from './useIsMounted'
|
|
35
33
|
export * from './useComponentTestId'
|
|
36
34
|
export * from './useId'
|
|
35
|
+
export * from './useAnimatedState'
|
|
36
|
+
export * from './useDebounceCallback'
|
|
37
|
+
export * from './useDerivedRef'
|
|
38
|
+
export * from './useDerivedState'
|
|
39
|
+
export * from './useFilteredList'
|
|
40
|
+
export * from './useLazyStore'
|
|
41
|
+
export * from './useOptions'
|
|
37
42
|
|
|
38
43
|
export {
|
|
39
44
|
useEffect,
|
package/src/onMount.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
import { AnyFunction } from '@codeleap/types'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Hook that runs a function once when the component mounts.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* onMount(() => {
|
|
9
|
+
* console.log('Component mounted')
|
|
10
|
+
* return () => console.log('Component unmounted')
|
|
11
|
+
* })
|
|
12
|
+
*/
|
|
4
13
|
export const onMount = (func: AnyFunction) => {
|
|
5
14
|
useEffect(() => {
|
|
6
15
|
return func()
|
package/src/onUpdate.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
import { AnyFunction } from '@codeleap/types'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Hook that runs a function when specified dependencies change.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* onUpdate(() => {
|
|
9
|
+
* console.log('Count changed:', count)
|
|
10
|
+
* }, [count])
|
|
11
|
+
*/
|
|
4
12
|
export const onUpdate = (func: AnyFunction, listeners = []) => {
|
|
5
13
|
useEffect(() => {
|
|
6
14
|
return func()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react'
|
|
2
|
+
import { SharedValue, useSharedValue, useDerivedValue, runOnJS, isSharedValue } from 'react-native-reanimated'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook that synchronizes a React state with a Reanimated SharedValue.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const [value, setValue, sharedValue] = useAnimatedState(0)
|
|
9
|
+
* setValue(10) // Updates both React state and SharedValue
|
|
10
|
+
*/
|
|
11
|
+
export function useAnimatedState<T>(initialValue: SharedValue<T> | T) {
|
|
12
|
+
const sharedValue = isSharedValue(initialValue)
|
|
13
|
+
? initialValue
|
|
14
|
+
: useSharedValue<T>(initialValue)
|
|
15
|
+
|
|
16
|
+
const [value, _setValue] = useState<T>(
|
|
17
|
+
isSharedValue(initialValue) ? initialValue.value : initialValue,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
useDerivedValue(() => {
|
|
21
|
+
runOnJS(_setValue)(sharedValue.value)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const setValue = useCallback((newValue: T) => {
|
|
25
|
+
sharedValue.value = newValue
|
|
26
|
+
if (isSharedValue(initialValue)) initialValue.value = newValue
|
|
27
|
+
_setValue(newValue)
|
|
28
|
+
}, [])
|
|
29
|
+
|
|
30
|
+
return [value, setValue, sharedValue] as const
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook that extracts and synchronizes the value from a SharedValue.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const value = useAnimatedValue(sharedValue)
|
|
38
|
+
*/
|
|
39
|
+
export function useAnimatedValue<T>(initialValue: SharedValue<T> | T): T {
|
|
40
|
+
const [value] = useAnimatedState(initialValue)
|
|
41
|
+
return value
|
|
42
|
+
}
|
package/src/useBooleanToggle.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hook that manages a boolean state with toggle functionality.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const [isOpen, toggleOpen] = useBooleanToggle(false)
|
|
8
|
+
* toggleOpen() // Toggles the value
|
|
9
|
+
* toggleOpen(true) // Sets to true
|
|
10
|
+
*/
|
|
3
11
|
export function useBooleanToggle(initial: boolean) {
|
|
4
12
|
const [v, setV] = useState(initial)
|
|
5
13
|
|
|
@@ -13,8 +13,7 @@ const simpleHash = (str: string): string => {
|
|
|
13
13
|
const normalizeProps = <P extends AnyRecord>(props: P, keys: Array<keyof P>): Partial<P> => {
|
|
14
14
|
return keys.reduce((acc, key) => {
|
|
15
15
|
if (key === 'children') {
|
|
16
|
-
acc[key] = Children.map(props[key], (child) =>
|
|
17
|
-
typeof child === 'object' ? '[Component]' : child
|
|
16
|
+
acc[key] = Children.map(props[key], (child) => typeof child === 'object' ? '[Component]' : child,
|
|
18
17
|
)
|
|
19
18
|
} else {
|
|
20
19
|
acc[key] = props[key]
|
|
@@ -28,17 +27,25 @@ const normalizeDebugName = (debugName: string) => {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
const generateComponentTestId = <P extends AnyRecord>(componentName: string, props: P, keys: Array<keyof P>) => {
|
|
31
|
-
const hasDebugName = typeof props?.
|
|
32
|
-
if (hasDebugName) return `${componentName}:${normalizeDebugName(props?.
|
|
30
|
+
const hasDebugName = typeof props?.debugName === 'string'
|
|
31
|
+
if (hasDebugName) return `${componentName}:${normalizeDebugName(props?.debugName)}`
|
|
33
32
|
const extractedProps = normalizeProps(props, keys)
|
|
34
33
|
return `${componentName}:${simpleHash(JSON.stringify(extractedProps))}`
|
|
35
34
|
}
|
|
36
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Hook that generates a stable test ID for a component based on its props.
|
|
38
|
+
* Uses debugName if available, otherwise generates a hash from specified prop keys.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const testId = useComponentTestId(Button, props, ['label', 'variant'])
|
|
42
|
+
* // Returns: "Button:debug-name" or "Button:123456"
|
|
43
|
+
*/
|
|
37
44
|
export const useComponentTestId = <P extends AnyRecord>(
|
|
38
45
|
Component: any,
|
|
39
46
|
props: P,
|
|
40
|
-
keys: Array<keyof P
|
|
47
|
+
keys: Array<keyof P>,
|
|
41
48
|
) => {
|
|
42
49
|
const testIdRef = useRef(generateComponentTestId(Component.styleRegistryName, props, keys))
|
|
43
50
|
return testIdRef.current
|
|
44
|
-
}
|
|
51
|
+
}
|
|
@@ -9,10 +9,21 @@ type UseConditionalStateOptions<T> = {
|
|
|
9
9
|
|
|
10
10
|
type SetState<T> = Dispatch<SetStateAction<T>> & ((value: T) => void) & (() => void)
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Hook that uses external state if provided, otherwise creates internal state.
|
|
14
|
+
* Useful for creating controlled/uncontrolled component patterns.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Controlled mode
|
|
18
|
+
* const [value, setValue] = useConditionalState(props.value, props.onChange)
|
|
19
|
+
*
|
|
20
|
+
* // Uncontrolled mode
|
|
21
|
+
* const [value, setValue] = useConditionalState(undefined, undefined, { initialValue: 'default' })
|
|
22
|
+
*/
|
|
12
23
|
export const useConditionalState = <T>(
|
|
13
24
|
value: T | undefined,
|
|
14
25
|
setter: AnyFunction,
|
|
15
|
-
options: UseConditionalStateOptions<T> = {}
|
|
26
|
+
options: UseConditionalStateOptions<T> = {},
|
|
16
27
|
): [T, SetState<T>] => {
|
|
17
28
|
const state = options?.isBooleanToggle
|
|
18
29
|
? useBooleanToggle(options?.initialValue as boolean)
|
package/src/useDebounce.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hook that debounces a value, updating it after a specified delay.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const [debouncedSearch, resetDebounce] = useDebounce(searchTerm, 500)
|
|
8
|
+
* // debouncedSearch updates 500ms after searchTerm stops changing
|
|
9
|
+
*/
|
|
3
10
|
export function useDebounce<T>(
|
|
4
11
|
value: T,
|
|
5
12
|
debounce: number,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook that creates debounced, flush, and cancel functions for a callback.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const { debounce, flush, cancel } = useDebounceCallback((value) => {
|
|
8
|
+
* console.log(value)
|
|
9
|
+
* }, 500)
|
|
10
|
+
*
|
|
11
|
+
* debounce('hello') // Will call callback after 500ms
|
|
12
|
+
* flush('immediate') // Calls callback immediately
|
|
13
|
+
* cancel() // Cancels pending debounced call
|
|
14
|
+
*/
|
|
15
|
+
export function useDebounceCallback<T extends any[]>(
|
|
16
|
+
callback: (...args: T) => void,
|
|
17
|
+
delay = 1000,
|
|
18
|
+
) {
|
|
19
|
+
const timeoutRef = useRef(null)
|
|
20
|
+
|
|
21
|
+
const debounce = useCallback((...args: T) => {
|
|
22
|
+
cancel()
|
|
23
|
+
|
|
24
|
+
timeoutRef.current = setTimeout(() => {
|
|
25
|
+
callback(...args)
|
|
26
|
+
timeoutRef.current = null
|
|
27
|
+
}, delay)
|
|
28
|
+
}, [callback])
|
|
29
|
+
|
|
30
|
+
const flush = useCallback((...args: T) => {
|
|
31
|
+
cancel()
|
|
32
|
+
callback(...args)
|
|
33
|
+
}, [callback])
|
|
34
|
+
|
|
35
|
+
const cancel = useCallback(() => {
|
|
36
|
+
if (timeoutRef.current) {
|
|
37
|
+
clearTimeout(timeoutRef.current)
|
|
38
|
+
timeoutRef.current = null
|
|
39
|
+
}
|
|
40
|
+
}, [])
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
debounce,
|
|
44
|
+
flush,
|
|
45
|
+
cancel,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook that creates a ref that automatically updates when a derived value changes.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const userNameRef = useDerivedRef(user, (u) => u.name)
|
|
8
|
+
* // userNameRef.current will always contain the latest user name
|
|
9
|
+
*/
|
|
10
|
+
export const useDerivedRef = <T, D>(
|
|
11
|
+
derivedValue: D,
|
|
12
|
+
getValue: (derivedValue: D) => T,
|
|
13
|
+
): React.MutableRefObject<T> => {
|
|
14
|
+
const ref = useRef(getValue(derivedValue))
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
ref.current = getValue(derivedValue)
|
|
18
|
+
}, [derivedValue])
|
|
19
|
+
|
|
20
|
+
return ref
|
|
21
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { deepEqual } from '@codeleap/utils'
|
|
2
|
+
import { useState, useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
type Options<T, D> = {
|
|
5
|
+
areEqual?: (currentState: T, derivedValue: D) => boolean
|
|
6
|
+
transform?: (value: D | T) => any
|
|
7
|
+
getValue?: (derivedValue: D) => T
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook that creates a state that synchronizes with a derived value, with customizable equality check.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const [state, setState] = useDerivedState(props.value, {
|
|
15
|
+
* getValue: (v) => v.id,
|
|
16
|
+
* areEqual: (a, b) => a === b.id
|
|
17
|
+
* })
|
|
18
|
+
*/
|
|
19
|
+
export const useDerivedState = <T, D = T>(
|
|
20
|
+
derivedValue: D,
|
|
21
|
+
options: Options<T, D> = {},
|
|
22
|
+
): [T, React.Dispatch<React.SetStateAction<T>>] => {
|
|
23
|
+
const {
|
|
24
|
+
getValue = (value: any) => value as T,
|
|
25
|
+
transform = (value) => value,
|
|
26
|
+
areEqual = (currentState, derivedValue) => deepEqual(currentState, derivedValue),
|
|
27
|
+
} = options
|
|
28
|
+
|
|
29
|
+
const [state, setState] = useState<T>(() => getValue(derivedValue))
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const newValue = getValue(derivedValue)
|
|
33
|
+
|
|
34
|
+
const stateAreEqual = areEqual(transform(state), transform(derivedValue))
|
|
35
|
+
|
|
36
|
+
if (!stateAreEqual) {
|
|
37
|
+
setState(newValue)
|
|
38
|
+
}
|
|
39
|
+
}, [derivedValue])
|
|
40
|
+
|
|
41
|
+
return [state, setState]
|
|
42
|
+
}
|
package/src/useEffectOnce.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { EffectCallback, useEffect } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hook that runs an effect only once when the component mounts.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* useEffectOnce(() => {
|
|
8
|
+
* console.log('Runs only once on mount')
|
|
9
|
+
* return () => console.log('Cleanup on unmount')
|
|
10
|
+
* })
|
|
11
|
+
*/
|
|
3
12
|
export const useEffectOnce = (effect: EffectCallback) => {
|
|
4
13
|
useEffect(effect, [])
|
|
5
14
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook that filters out the first item from a list that matches a predicate.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const filteredUsers = useFilteredList(users, (user) => user.id === deletedId)
|
|
8
|
+
* // Returns users array without the first user matching the predicate
|
|
9
|
+
*/
|
|
10
|
+
export function useFilteredList<T>(list: T[], predicate: (item: T) => boolean): T[] {
|
|
11
|
+
return useMemo(() => {
|
|
12
|
+
if (!list) return []
|
|
13
|
+
|
|
14
|
+
const index = list.findIndex(predicate)
|
|
15
|
+
if (index === -1) return list
|
|
16
|
+
|
|
17
|
+
const newList = [...list]
|
|
18
|
+
newList.splice(index, 1)
|
|
19
|
+
return newList
|
|
20
|
+
}, [list, predicate])
|
|
21
|
+
}
|
package/src/useForceRender.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { useReducer } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hook that returns a function to force a component re-render.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const forceRender = useForceRender()
|
|
8
|
+
* forceRender() // Forces component to re-render
|
|
9
|
+
*/
|
|
3
10
|
export function useForceRender() {
|
|
4
11
|
const [_, forceRender] = useReducer((x) => x + 1, 0)
|
|
5
12
|
return forceRender
|
package/src/useId.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { useRef, useId as _useId } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hook that returns a stable ID, using provided ID if available or generating one.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const id = useId('custom-id') // Returns 'custom-id'
|
|
8
|
+
* const id = useId() // Returns generated ID like ':r1:'
|
|
9
|
+
*/
|
|
3
10
|
export function useId<T>(id?: T) {
|
|
4
11
|
const defaultId = _useId()
|
|
5
12
|
const idRef = useRef(id)
|
|
6
13
|
|
|
7
14
|
return idRef.current ?? defaultId
|
|
8
|
-
}
|
|
15
|
+
}
|
package/src/useInterval.ts
CHANGED
|
@@ -1,17 +1,51 @@
|
|
|
1
|
-
import { useRef } from 'react'
|
|
1
|
+
import { useRef, useCallback, useEffect } from 'react'
|
|
2
2
|
import { AnyFunction } from '@codeleap/types'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const intervalRef = useRef(null)
|
|
4
|
+
type UseIntervalHandler = (clear: AnyFunction) => void
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Hook that manages an interval with start and clear controls.
|
|
8
|
+
* The handler receives a clear function to stop the interval from within.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const { start, clear } = useInterval((clearFn) => {
|
|
12
|
+
* console.log('Tick')
|
|
13
|
+
* if (shouldStop) clearFn()
|
|
14
|
+
* }, 1000)
|
|
15
|
+
*
|
|
16
|
+
* start() // Starts the interval
|
|
17
|
+
* clear() // Stops the interval
|
|
18
|
+
*/
|
|
19
|
+
export function useInterval(handler: UseIntervalHandler, interval: number) {
|
|
20
|
+
const intervalRef = useRef<NodeJS.Timer | null>(null)
|
|
21
|
+
const handlerRef = useRef<UseIntervalHandler>(handler)
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
handlerRef.current = handler
|
|
25
|
+
}, [handler])
|
|
26
|
+
|
|
27
|
+
const clear = useCallback(() => {
|
|
28
|
+
if (intervalRef.current != null) {
|
|
29
|
+
clearInterval(intervalRef.current)
|
|
30
|
+
intervalRef.current = null
|
|
31
|
+
}
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
const start = useCallback(() => {
|
|
35
|
+
clear()
|
|
36
|
+
|
|
37
|
+
if (intervalRef.current == null) {
|
|
38
|
+
intervalRef.current = setInterval(() => {
|
|
39
|
+
handlerRef.current(clear)
|
|
40
|
+
}, interval)
|
|
41
|
+
}
|
|
42
|
+
}, [])
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
return () => {
|
|
46
|
+
clear()
|
|
47
|
+
}
|
|
48
|
+
}, [clear])
|
|
15
49
|
|
|
16
50
|
return {
|
|
17
51
|
clear,
|
package/src/useIsMounted.ts
CHANGED
|
@@ -7,7 +7,12 @@ const useIsomorphicLayoutEffect =
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Hook to check if the component has mounted. SSR safe.
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const isMounted = useIsMounted()
|
|
13
|
+
* if (isMounted) {
|
|
14
|
+
* // Safe to use browser APIs
|
|
15
|
+
* }
|
|
11
16
|
*/
|
|
12
17
|
export function useIsMounted() {
|
|
13
18
|
const [mounted, setMounted] = useState(false)
|
|
@@ -22,7 +27,12 @@ export function useIsMounted() {
|
|
|
22
27
|
/**
|
|
23
28
|
* Hook to check if the code is running on the client-side.
|
|
24
29
|
* NOTE: This is just a mirror of `useIsMounted` for retrocompatibility reasons
|
|
25
|
-
*
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const { isClient } = useIsClient()
|
|
33
|
+
* if (isClient) {
|
|
34
|
+
* // Safe to use browser APIs
|
|
35
|
+
* }
|
|
26
36
|
*/
|
|
27
37
|
export function useIsClient() {
|
|
28
38
|
return {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { globalState } from '@codeleap/store'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook that lazily creates a global store with an initial value.
|
|
6
|
+
* The store is created only once and persists across re-renders.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const counterStore = useLazyStore(0)
|
|
10
|
+
* // Store is created once and can be used across components
|
|
11
|
+
*/
|
|
12
|
+
export function useLazyStore<T>(initialValue: T) {
|
|
13
|
+
const store = useMemo(() => {
|
|
14
|
+
return globalState(initialValue)
|
|
15
|
+
}, [])
|
|
16
|
+
|
|
17
|
+
return store
|
|
18
|
+
}
|
package/src/useModal.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { TypeGuards } from '@codeleap/types'
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Hook that manages modal visibility state with open, close, and toggle functions.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const modal = useModal()
|
|
9
|
+
* modal.open() // Opens modal
|
|
10
|
+
* modal.close() // Closes modal
|
|
11
|
+
* modal.toggle() // Toggles visibility
|
|
12
|
+
* modal.toggle(true) // Forces open
|
|
13
|
+
*/
|
|
4
14
|
export function useModal(startsOpen = false) {
|
|
5
15
|
const [visible, setVisible] = useState(startsOpen)
|
|
6
16
|
|
|
@@ -17,7 +27,7 @@ export function useModal(startsOpen = false) {
|
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
return {
|
|
20
|
-
visible,
|
|
30
|
+
visible,
|
|
21
31
|
toggle,
|
|
22
32
|
open,
|
|
23
33
|
close,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook that manages selected option state with boolean flags for each option.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const { selectedOption, setSelectedOption, isSelected } = useOptions(['light', 'dark'] as const)
|
|
8
|
+
* // isSelected = { light: true, dark: false }
|
|
9
|
+
* setSelectedOption('dark')
|
|
10
|
+
* // isSelected = { light: false, dark: true }
|
|
11
|
+
*/
|
|
12
|
+
export function useOptions<T extends string>(options: readonly T[], initialOptions: T = options[0]) {
|
|
13
|
+
const [selectedOption, setSelectedOption] = useState<T>(initialOptions)
|
|
14
|
+
|
|
15
|
+
const isSelected = useMemo(() => {
|
|
16
|
+
return options.reduce((acc, option) => {
|
|
17
|
+
acc[option] = option === selectedOption
|
|
18
|
+
return acc
|
|
19
|
+
}, {} as Record<T, boolean>)
|
|
20
|
+
}, [selectedOption, options])
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
selectedOption,
|
|
24
|
+
setSelectedOption,
|
|
25
|
+
isSelected,
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/usePartialState.ts
CHANGED
|
@@ -4,6 +4,14 @@ import { deepMerge } from '@codeleap/utils'
|
|
|
4
4
|
|
|
5
5
|
type SetPartialStateCallback<T> = (value: T) => DeepPartial<T>
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Hook that manages state with partial updates using deep merge.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const [user, setUser] = usePartialState({ name: 'John', age: 30 })
|
|
12
|
+
* setUser({ age: 31 }) // Only updates age, keeps name
|
|
13
|
+
* setUser(prev => ({ age: prev.age + 1 })) // Functional update
|
|
14
|
+
*/
|
|
7
15
|
export function usePartialState<T= any>(initial: T | (() => T)) {
|
|
8
16
|
const [state, setState] = useState(initial)
|
|
9
17
|
|
package/src/usePlaces.ts
CHANGED
|
@@ -13,6 +13,9 @@ type Params = {
|
|
|
13
13
|
showDetails?: boolean
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves detailed information for a specific Google Place.
|
|
18
|
+
*/
|
|
16
19
|
export const retrievePlaceDetails = async (placeId: string, apiKey: string) => {
|
|
17
20
|
const response = await axios.get(BASE_URL_DETAILS, {
|
|
18
21
|
params: {
|
|
@@ -24,6 +27,10 @@ export const retrievePlaceDetails = async (placeId: string, apiKey: string) => {
|
|
|
24
27
|
return response?.data?.result
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves places from Google Places API or Geocoding API.
|
|
32
|
+
* Supports both text search and lat/lng coordinates.
|
|
33
|
+
*/
|
|
27
34
|
export const retrievePlaces = async (params: Params) => {
|
|
28
35
|
let response
|
|
29
36
|
const inputWithoutSpaces = params?.input?.replace(/\s/g, '')
|
|
@@ -53,6 +60,17 @@ export const retrievePlaces = async (params: Params) => {
|
|
|
53
60
|
|
|
54
61
|
export type UsePlacesParams = Params
|
|
55
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Hook that fetches Google Places using React Query.
|
|
65
|
+
* Automatically handles caching and refetching.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* const places = usePlaces({
|
|
69
|
+
* input: 'New York',
|
|
70
|
+
* key: 'YOUR_API_KEY',
|
|
71
|
+
* showDetails: true
|
|
72
|
+
* })
|
|
73
|
+
*/
|
|
56
74
|
export const usePlaces = (params: Params) => {
|
|
57
75
|
const places = useQuery({
|
|
58
76
|
queryKey: ['places', params],
|
|
@@ -7,6 +7,22 @@ export type UsePlacesAutocompleteUtilsProps<T extends Record<string, any>> = {
|
|
|
7
7
|
onPress?: (address: string, item: T) => void
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Hook that manages address autocomplete state with debounced input handling.
|
|
12
|
+
* Useful for Google Places autocomplete implementations.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const {
|
|
16
|
+
* address,
|
|
17
|
+
* handleChangeAddress,
|
|
18
|
+
* handlePressAddress,
|
|
19
|
+
* isTyping
|
|
20
|
+
* } = usePlacesAutocompleteUtils({
|
|
21
|
+
* debounce: 500,
|
|
22
|
+
* onValueChange: (addr) => fetchPlaces(addr),
|
|
23
|
+
* onPress: (addr, item) => console.log('Selected:', item)
|
|
24
|
+
* })
|
|
25
|
+
*/
|
|
10
26
|
export const usePlacesAutocompleteUtils = <T extends Record<string, any>>(props: UsePlacesAutocompleteUtilsProps<T>) => {
|
|
11
27
|
const {
|
|
12
28
|
debounce = 250,
|
package/src/usePrevious.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hook that returns the previous value of a variable.
|
|
5
|
+
* The value is updated after render is committed to the DOM.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const [count, setCount] = useState(0)
|
|
9
|
+
* const prevCount = usePrevious(count)
|
|
10
|
+
* // On first render: count=0, prevCount=null
|
|
11
|
+
* // After setCount(1): count=1, prevCount=0
|
|
12
|
+
*/
|
|
3
13
|
export const usePrevious = <T>(value: T) => {
|
|
4
|
-
const ref = useRef<T>()
|
|
14
|
+
const ref = useRef<T>(null)
|
|
5
15
|
useEffect(() => {
|
|
6
16
|
ref.current = value
|
|
7
17
|
})
|
package/src/usePromise.ts
CHANGED
|
@@ -8,10 +8,24 @@ type UsePromiseOptions<T = any> = {
|
|
|
8
8
|
debugName?: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Hook that creates a deferred promise with manual resolve/reject control.
|
|
13
|
+
* Useful for coordinating asynchronous operations across component lifecycle.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const promise = usePromise({ timeout: 5000 })
|
|
17
|
+
* const handleClick = async () => {
|
|
18
|
+
* const result = await promise._await()
|
|
19
|
+
* console.log(result)
|
|
20
|
+
* }
|
|
21
|
+
* // Later, from another callback:
|
|
22
|
+
* promise.resolve('success')
|
|
23
|
+
*/
|
|
11
24
|
export const usePromise = <T = any>(options?: UsePromiseOptions<T>) => {
|
|
12
|
-
const rejectRef = useRef<AnyFunction>()
|
|
13
|
-
const resolveRef = useRef<(v:T) => any>()
|
|
25
|
+
const rejectRef = useRef<AnyFunction>(null)
|
|
26
|
+
const resolveRef = useRef<(v:T) => any>(null)
|
|
14
27
|
const timeoutRef = useRef(null)
|
|
28
|
+
|
|
15
29
|
const reject = async (err: any) => {
|
|
16
30
|
await rejectRef.current?.(err)
|
|
17
31
|
options?.onReject?.(err)
|
package/src/useSearch/index.ts
CHANGED
|
@@ -3,6 +3,22 @@ import { useBooleanToggle } from '../useBooleanToggle'
|
|
|
3
3
|
import { useSearchParams } from './types'
|
|
4
4
|
import { TypeGuards } from '@codeleap/types'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Hook that manages searchable select/autocomplete state with async loading support.
|
|
8
|
+
* Handles both local filtering and remote data fetching.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const search = useSearch({
|
|
12
|
+
* value: 'selected-value',
|
|
13
|
+
* multiple: false,
|
|
14
|
+
* defaultOptions: [...],
|
|
15
|
+
* loadOptions: async (query) => fetchOptions(query),
|
|
16
|
+
* filterItems: (query, opts) => opts.filter(o => o.label.includes(query))
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* search.onChangeSearch('query')
|
|
20
|
+
* search.load()
|
|
21
|
+
*/
|
|
6
22
|
export function useSearch<T extends string|number = string, Multi extends boolean = false>(props: useSearchParams<T, Multi>) {
|
|
7
23
|
const {
|
|
8
24
|
value,
|
package/src/useToggle.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hook that toggles between two values.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const [theme, toggleTheme] = useToggle(['light', 'dark'] as const, 'light')
|
|
8
|
+
* toggleTheme() // Switches to 'dark'
|
|
9
|
+
* toggleTheme('light') // Sets to 'light'
|
|
10
|
+
*/
|
|
3
11
|
export function useToggle<T extends readonly [any, any], V extends T[0] | T[1]>(
|
|
4
12
|
options: T,
|
|
5
13
|
initial: V,
|
package/src/useUncontrolled.ts
CHANGED
|
@@ -11,6 +11,18 @@ export interface UncontrolledOptions<T> {
|
|
|
11
11
|
rule: (value: T | null | undefined) => boolean
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Hook that manages controlled/uncontrolled component pattern with smooth transitions.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const [value, handleChange, mode] = useUncontrolled({
|
|
19
|
+
* value: props.value,
|
|
20
|
+
* defaultValue: 'default',
|
|
21
|
+
* finalValue: '',
|
|
22
|
+
* rule: (v) => v !== undefined,
|
|
23
|
+
* onChange: (v) => props.onChange?.(v)
|
|
24
|
+
* })
|
|
25
|
+
*/
|
|
14
26
|
export function useUncontrolled<T>({
|
|
15
27
|
value,
|
|
16
28
|
defaultValue,
|
package/src/useUnmount.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { useRef } from 'react'
|
|
2
2
|
import { useEffectOnce } from './useEffectOnce'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Hook that runs a cleanup function when the component unmounts.
|
|
6
|
+
* The function reference is updated on each render to always use the latest version.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* useUnmount(() => {
|
|
10
|
+
* console.log('Component unmounting')
|
|
11
|
+
* // Cleanup logic here
|
|
12
|
+
* })
|
|
13
|
+
*/
|
|
4
14
|
export const useUnmount = (fn: () => any): void => {
|
|
5
15
|
const fnRef = useRef(fn)
|
|
6
16
|
|
package/src/useCounter.ts
DELETED
package/src/useListState.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
|
|
3
|
-
export interface UseListStateHandler<T> {
|
|
4
|
-
setState: React.Dispatch<React.SetStateAction<T[]>>
|
|
5
|
-
append: (...items: T[]) => void
|
|
6
|
-
prepend: (...items: T[]) => void
|
|
7
|
-
insert: (index: number, ...items: T[]) => void
|
|
8
|
-
pop: () => void
|
|
9
|
-
shift: () => void
|
|
10
|
-
apply: (fn: (item: T, index?: number) => T) => void
|
|
11
|
-
applyWhere: (
|
|
12
|
-
condition: (item: T, index: number) => boolean,
|
|
13
|
-
fn: (item: T, index?: number) => T
|
|
14
|
-
) => void
|
|
15
|
-
remove: (...indices: number[]) => void
|
|
16
|
-
reorder: ({ from, to }: { from: number; to: number }) => void
|
|
17
|
-
setItem: (index: number, item: T) => void
|
|
18
|
-
setItemProp: <K extends keyof T, U extends T[K]>(index: number, prop: K, value: U) => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type UseListState<T> = [T[], UseListStateHandler<T>]
|
|
22
|
-
|
|
23
|
-
export function useListState<T>(initialValue: (T[] | (() => T[])) = []): UseListState<T> {
|
|
24
|
-
const [state, setState] = useState(initialValue)
|
|
25
|
-
|
|
26
|
-
const append = (...items: T[]) => setState((current) => [...current, ...items])
|
|
27
|
-
const prepend = (...items: T[]) => setState((current) => [...items, ...current])
|
|
28
|
-
|
|
29
|
-
const insert = (index: number, ...items: T[]) => setState((current) => [...current.slice(0, index), ...items, ...current.slice(index)])
|
|
30
|
-
|
|
31
|
-
const apply = (fn: (item: T, index?: number) => T) => setState((current) => current.map((item, index) => fn(item, index)))
|
|
32
|
-
|
|
33
|
-
const remove = (...indices: number[]) => setState((current) => current.filter((_, index) => !indices.includes(index)))
|
|
34
|
-
|
|
35
|
-
const pop = () => setState((current) => {
|
|
36
|
-
const cloned = [...current]
|
|
37
|
-
cloned.pop()
|
|
38
|
-
return cloned
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const shift = () => setState((current) => {
|
|
42
|
-
const cloned = [...current]
|
|
43
|
-
cloned.shift()
|
|
44
|
-
return cloned
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const reorder = ({ from, to }: { from: number; to: number }) => setState((current) => {
|
|
48
|
-
const cloned = [...current]
|
|
49
|
-
const item = current[from]
|
|
50
|
-
|
|
51
|
-
cloned.splice(from, 1)
|
|
52
|
-
cloned.splice(to, 0, item)
|
|
53
|
-
|
|
54
|
-
return cloned
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const setItem = (index: number, item: T) => setState((current) => {
|
|
58
|
-
const cloned = [...current]
|
|
59
|
-
cloned[index] = item
|
|
60
|
-
return cloned
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const setItemProp = <K extends keyof T, U extends T[K]>(index: number, prop: K, value: U) => setState((current) => {
|
|
64
|
-
const cloned = [...current]
|
|
65
|
-
cloned[index] = { ...cloned[index], [prop]: value }
|
|
66
|
-
return cloned
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
const applyWhere = (
|
|
70
|
-
condition: (item: T, index: number) => boolean,
|
|
71
|
-
fn: (item: T, index?: number) => T,
|
|
72
|
-
) => setState((current) => current.map((item, index) => (condition(item, index) ? fn(item, index) : item)),
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
return [
|
|
76
|
-
state,
|
|
77
|
-
{
|
|
78
|
-
setState,
|
|
79
|
-
append,
|
|
80
|
-
prepend,
|
|
81
|
-
insert,
|
|
82
|
-
pop,
|
|
83
|
-
shift,
|
|
84
|
-
apply,
|
|
85
|
-
applyWhere,
|
|
86
|
-
remove,
|
|
87
|
-
reorder,
|
|
88
|
-
setItem,
|
|
89
|
-
setItemProp,
|
|
90
|
-
},
|
|
91
|
-
]
|
|
92
|
-
}
|