react-simplikit 0.0.2 → 0.0.4
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/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/components/ImpressionArea/index.d.ts +46 -0
- package/dist/components/ImpressionArea/index.js +276 -0
- package/dist/components/Separated/index.d.ts +38 -0
- package/dist/components/Separated/index.js +40 -0
- package/dist/components/SwitchCase/index.d.ts +46 -0
- package/dist/components/SwitchCase/index.js +35 -0
- package/dist/hooks/useAsyncEffect/index.d.ts +25 -0
- package/dist/hooks/useAsyncEffect/index.js +43 -0
- package/dist/hooks/useBooleanState/index.d.ts +19 -0
- package/dist/hooks/useBooleanState/index.js +45 -0
- package/dist/hooks/useCallbackOncePerRender/index.d.ts +41 -0
- package/dist/hooks/useCallbackOncePerRender/index.js +59 -0
- package/dist/hooks/useDebounce/index.d.ts +48 -0
- package/dist/hooks/useDebounce/index.js +123 -0
- package/dist/hooks/useImpressionRef/index.d.ts +38 -0
- package/dist/hooks/useImpressionRef/index.js +237 -0
- package/dist/hooks/useInputState/index.d.ts +27 -0
- package/dist/hooks/useInputState/index.js +45 -0
- package/dist/hooks/useIntersectionObserver/index.d.ts +33 -0
- package/dist/hooks/useIntersectionObserver/index.js +91 -0
- package/dist/hooks/useInterval/index.d.ts +37 -0
- package/dist/hooks/useInterval/index.js +64 -0
- package/dist/hooks/useLoading/index.d.ts +37 -0
- package/dist/hooks/useLoading/index.js +61 -0
- package/dist/hooks/useOutsideClickEffect/index.d.ts +27 -0
- package/dist/hooks/useOutsideClickEffect/index.js +70 -0
- package/dist/hooks/usePreservedCallback/index.d.ts +30 -0
- package/dist/hooks/usePreservedCallback/index.js +41 -0
- package/dist/hooks/usePreservedReference/index.d.ts +42 -0
- package/dist/hooks/usePreservedReference/index.js +44 -0
- package/dist/hooks/usePrevious/index.d.ts +21 -0
- package/dist/hooks/usePrevious/index.js +47 -0
- package/dist/hooks/useRefEffect/index.d.ts +31 -0
- package/dist/hooks/useRefEffect/index.js +68 -0
- package/dist/hooks/useStorageState/index.d.ts +50 -0
- package/dist/hooks/useStorageState/index.js +168 -0
- package/dist/hooks/useThrottle/index.d.ts +47 -0
- package/dist/hooks/useThrottle/index.js +147 -0
- package/dist/hooks/useTimeout/index.d.ts +30 -0
- package/dist/hooks/useTimeout/index.js +53 -0
- package/dist/hooks/useToggle/index.d.ts +30 -0
- package/dist/hooks/useToggle/index.js +36 -0
- package/dist/hooks/useVisibilityEvent/index.d.ts +27 -0
- package/dist/hooks/useVisibilityEvent/index.js +46 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +761 -0
- package/dist/utils/buildContext/index.d.ts +38 -0
- package/dist/utils/buildContext/index.js +55 -0
- package/dist/utils/mergeRefs/index.d.ts +37 -0
- package/dist/utils/mergeRefs/index.js +45 -0
- package/esm/components/ImpressionArea/index.d.mts +46 -0
- package/esm/components/ImpressionArea/index.mjs +249 -0
- package/esm/components/Separated/index.d.mts +38 -0
- package/esm/components/Separated/index.mjs +13 -0
- package/esm/components/SwitchCase/index.d.mts +46 -0
- package/esm/components/SwitchCase/index.mjs +8 -0
- package/esm/hooks/useAsyncEffect/index.d.mts +25 -0
- package/esm/hooks/useAsyncEffect/index.mjs +16 -0
- package/esm/hooks/useBooleanState/index.d.mts +19 -0
- package/esm/hooks/useBooleanState/index.mjs +18 -0
- package/esm/hooks/useCallbackOncePerRender/index.d.mts +41 -0
- package/esm/hooks/useCallbackOncePerRender/index.mjs +32 -0
- package/esm/hooks/useDebounce/index.d.mts +48 -0
- package/esm/hooks/useDebounce/index.mjs +96 -0
- package/esm/hooks/useImpressionRef/index.d.mts +38 -0
- package/esm/hooks/useImpressionRef/index.mjs +210 -0
- package/esm/hooks/useInputState/index.d.mts +27 -0
- package/esm/hooks/useInputState/index.mjs +18 -0
- package/esm/hooks/useIntersectionObserver/index.d.mts +33 -0
- package/esm/hooks/useIntersectionObserver/index.mjs +64 -0
- package/esm/hooks/useInterval/index.d.mts +37 -0
- package/esm/hooks/useInterval/index.mjs +37 -0
- package/esm/hooks/useLoading/index.d.mts +37 -0
- package/esm/hooks/useLoading/index.mjs +34 -0
- package/esm/hooks/useOutsideClickEffect/index.d.mts +27 -0
- package/esm/hooks/useOutsideClickEffect/index.mjs +43 -0
- package/esm/hooks/usePreservedCallback/index.d.mts +30 -0
- package/esm/hooks/usePreservedCallback/index.mjs +14 -0
- package/esm/hooks/usePreservedReference/index.d.mts +42 -0
- package/esm/hooks/usePreservedReference/index.mjs +17 -0
- package/esm/hooks/usePrevious/index.d.mts +21 -0
- package/esm/hooks/usePrevious/index.mjs +20 -0
- package/esm/hooks/useRefEffect/index.d.mts +31 -0
- package/esm/hooks/useRefEffect/index.mjs +41 -0
- package/esm/hooks/useStorageState/index.d.mts +50 -0
- package/esm/hooks/useStorageState/index.mjs +141 -0
- package/esm/hooks/useThrottle/index.d.mts +47 -0
- package/esm/hooks/useThrottle/index.mjs +120 -0
- package/esm/hooks/useTimeout/index.d.mts +30 -0
- package/esm/hooks/useTimeout/index.mjs +26 -0
- package/esm/hooks/useToggle/index.d.mts +30 -0
- package/esm/hooks/useToggle/index.mjs +9 -0
- package/esm/hooks/useVisibilityEvent/index.d.mts +27 -0
- package/esm/hooks/useVisibilityEvent/index.mjs +19 -0
- package/esm/index.d.mts +26 -0
- package/esm/index.mjs +711 -0
- package/esm/utils/buildContext/index.d.mts +38 -0
- package/esm/utils/buildContext/index.mjs +28 -0
- package/esm/utils/mergeRefs/index.d.mts +37 -0
- package/esm/utils/mergeRefs/index.mjs +18 -0
- package/package.json +87 -7
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description
|
|
3
|
+
* `useLoading` is a React hook that simplifies managing the loading state of a `Promise`.
|
|
4
|
+
* It provides a state to track whether an asynchronous operation is in progress and a function to handle the loading state automatically.
|
|
5
|
+
*
|
|
6
|
+
* @returns A tuple `[boolean, <T>(promise: Promise<T>) => Promise<T>]`:
|
|
7
|
+
*
|
|
8
|
+
* - `boolean`: Represents the current loading state.
|
|
9
|
+
* - The initial value is `false`.
|
|
10
|
+
* - It is set to `true` when an asynchronous task is in progress.
|
|
11
|
+
*
|
|
12
|
+
* - `<T>(promise: Promise<T>) => Promise<T>`: A function that executes asynchronous tasks while managing the loading state.
|
|
13
|
+
* - This function takes a `Promise` as an argument and automatically resets the `isLoading` state to `false` when the `Promise` completes.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* function ConfirmButton() {
|
|
17
|
+
* const [loading, startLoading] = useLoading();
|
|
18
|
+
*
|
|
19
|
+
* const handleSubmit = useCallback(async () => {
|
|
20
|
+
* try {
|
|
21
|
+
* const result = await startLoading(postConfirmation());
|
|
22
|
+
* router.push(`/success?id=${result.id}`);
|
|
23
|
+
* } catch (error) {
|
|
24
|
+
* console.error('Error:', error);
|
|
25
|
+
* }
|
|
26
|
+
* }, [startLoading]);
|
|
27
|
+
*
|
|
28
|
+
* return (
|
|
29
|
+
* <button disabled={loading} onClick={handleSubmit}>
|
|
30
|
+
* {loading ? 'Loading...' : 'Confirm'}
|
|
31
|
+
* </button>
|
|
32
|
+
* );
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
declare function useLoading(): [boolean, <T>(promise: Promise<T>) => Promise<T>];
|
|
36
|
+
|
|
37
|
+
export { useLoading };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/hooks/useLoading/useLoading.ts
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
function useLoading() {
|
|
4
|
+
const [loading, setLoading] = useState(false);
|
|
5
|
+
const ref = useIsMountedRef();
|
|
6
|
+
const startTransition = useCallback(
|
|
7
|
+
async (promise) => {
|
|
8
|
+
try {
|
|
9
|
+
setLoading(true);
|
|
10
|
+
const data = await promise;
|
|
11
|
+
return data;
|
|
12
|
+
} finally {
|
|
13
|
+
if (ref.isMounted) {
|
|
14
|
+
setLoading(false);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
[ref.isMounted]
|
|
19
|
+
);
|
|
20
|
+
return useMemo(() => [loading, startTransition], [loading, startTransition]);
|
|
21
|
+
}
|
|
22
|
+
function useIsMountedRef() {
|
|
23
|
+
const ref = useRef({ isMounted: true }).current;
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
ref.isMounted = true;
|
|
26
|
+
return () => {
|
|
27
|
+
ref.isMounted = false;
|
|
28
|
+
};
|
|
29
|
+
}, [ref]);
|
|
30
|
+
return ref;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
useLoading
|
|
34
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type OneOrMore<T> = T | T[];
|
|
2
|
+
/**
|
|
3
|
+
* @description
|
|
4
|
+
* `useOutsideClickEffect` is a React hook that triggers a callback when a click event occurs outside the specified container(s).
|
|
5
|
+
* It is useful for closing modals, dropdowns, tooltips, and other UI components when clicking outside.
|
|
6
|
+
*
|
|
7
|
+
* @param {HTMLElement | HTMLElement[] | null} container - A single HTML element, an array of HTML elements, or `null`.
|
|
8
|
+
* If `null`, no event listener is attached.
|
|
9
|
+
* @param {() => void} callback - A function that is executed when clicking outside the specified container(s).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { useOutsideClickEffect } from 'react-simplikit';
|
|
13
|
+
* import { useState } from 'react';
|
|
14
|
+
*
|
|
15
|
+
* function Example() {
|
|
16
|
+
* const [wrapperEl, setWrapperEl] = useState<HTMLDivElement | null>(null);
|
|
17
|
+
*
|
|
18
|
+
* useOutsideClickEffect(wrapperEl, () => {
|
|
19
|
+
* console.log('Outside clicked!');
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* return <div ref={setWrapperEl}>Content</div>;
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
declare function useOutsideClickEffect(container: OneOrMore<HTMLElement | null>, callback: () => void): void;
|
|
26
|
+
|
|
27
|
+
export { useOutsideClickEffect };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/hooks/useOutsideClickEffect/useOutsideClickEffect.ts
|
|
2
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/hooks/usePreservedCallback/usePreservedCallback.ts
|
|
5
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
6
|
+
function usePreservedCallback(callback) {
|
|
7
|
+
const callbackRef = useRef(callback);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
callbackRef.current = callback;
|
|
10
|
+
}, [callback]);
|
|
11
|
+
return useCallback((...args) => {
|
|
12
|
+
return callbackRef.current(...args);
|
|
13
|
+
}, []);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/hooks/useOutsideClickEffect/useOutsideClickEffect.ts
|
|
17
|
+
function useOutsideClickEffect(container, callback) {
|
|
18
|
+
const containers = useRef2([]);
|
|
19
|
+
const handleDocumentClick = usePreservedCallback(({ target }) => {
|
|
20
|
+
if (target === null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (containers.current.length === 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (containers.current.some((x) => x.contains(target))) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
callback();
|
|
30
|
+
});
|
|
31
|
+
useEffect2(() => {
|
|
32
|
+
containers.current = [container].flat(1).filter((item) => item != null);
|
|
33
|
+
}, [container]);
|
|
34
|
+
useEffect2(() => {
|
|
35
|
+
document.addEventListener("click", handleDocumentClick);
|
|
36
|
+
return () => {
|
|
37
|
+
document.removeEventListener("click", handleDocumentClick);
|
|
38
|
+
};
|
|
39
|
+
}, [handleDocumentClick]);
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
useOutsideClickEffect
|
|
43
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description
|
|
3
|
+
* `usePreservedCallback` is a React hook that maintains a stable reference to a callback function
|
|
4
|
+
* while ensuring it always has access to the latest state or props. This prevents unnecessary re-renders
|
|
5
|
+
* and simplifies dependency management when passing callbacks to child components or handling event listeners.
|
|
6
|
+
*
|
|
7
|
+
* @param {(...args: any[]) => any} callback - The function to preserve.
|
|
8
|
+
* It always references the latest state or props, even when the component re-renders.
|
|
9
|
+
*
|
|
10
|
+
* @returns {(...args: any[]) => any} A function with the same signature as the input callback.
|
|
11
|
+
* The returned function maintains a stable reference while accessing the latest state or props.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { usePreservedCallback } from 'react-simplikit';
|
|
15
|
+
* import { useState } from 'react';
|
|
16
|
+
*
|
|
17
|
+
* function Counter() {
|
|
18
|
+
* const [count, setCount] = useState(0);
|
|
19
|
+
*
|
|
20
|
+
* const handleClick = usePreservedCallback(() => {
|
|
21
|
+
* console.log(`Current count: ${count}`);
|
|
22
|
+
* setCount(prev => prev + 1);
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* return <button onClick={handleClick}>Click me</button>;
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
declare function usePreservedCallback<Arguments extends any[] = any[], ReturnValue = unknown>(callback: (...args: Arguments) => ReturnValue): (...args: Arguments) => ReturnValue;
|
|
29
|
+
|
|
30
|
+
export { usePreservedCallback };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/hooks/usePreservedCallback/usePreservedCallback.ts
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
+
function usePreservedCallback(callback) {
|
|
4
|
+
const callbackRef = useRef(callback);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
callbackRef.current = callback;
|
|
7
|
+
}, [callback]);
|
|
8
|
+
return useCallback((...args) => {
|
|
9
|
+
return callbackRef.current(...args);
|
|
10
|
+
}, []);
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
usePreservedCallback
|
|
14
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type NotNullishValue = {};
|
|
2
|
+
/**
|
|
3
|
+
* @description
|
|
4
|
+
* `usePreservedReference` is a React hook that helps maintain the reference of a value
|
|
5
|
+
* when it hasn't changed, while ensuring you can safely use the latest state.
|
|
6
|
+
* It prevents unnecessary re-renders while always allowing access to the latest data.
|
|
7
|
+
*
|
|
8
|
+
* @param {T} value - The value to maintain the reference for. It returns a new reference
|
|
9
|
+
* if the state value changes after comparison.
|
|
10
|
+
* @param {(a: T, b: T) => boolean} [areValuesEqual] - An optional function to determine
|
|
11
|
+
* if two values are equal. By default, it uses `JSON.stringify` for comparison.
|
|
12
|
+
*
|
|
13
|
+
* @returns {T} Returns the same reference if the value is considered equal to the previous one,
|
|
14
|
+
* otherwise returns a new reference.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* import { usePreservedReference } from 'react-simplikit';
|
|
18
|
+
* import { useState } from 'react';
|
|
19
|
+
*
|
|
20
|
+
* function ExampleComponent() {
|
|
21
|
+
* const [state, setState] = useState({ key: 'value' });
|
|
22
|
+
*
|
|
23
|
+
* const preservedState = usePreservedReference(state);
|
|
24
|
+
*
|
|
25
|
+
* return <div>{preservedState.key}</div>;
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* import { usePreservedReference } from 'react-simplikit';
|
|
30
|
+
* import { useState } from 'react';
|
|
31
|
+
*
|
|
32
|
+
* function ExampleComponent() {
|
|
33
|
+
* const [state, setState] = useState({ key: 'value' });
|
|
34
|
+
*
|
|
35
|
+
* const preservedState = usePreservedReference(state, (a, b) => a.key === b.key);
|
|
36
|
+
*
|
|
37
|
+
* return <div>{preservedState.key}</div>;
|
|
38
|
+
* }
|
|
39
|
+
*/
|
|
40
|
+
declare function usePreservedReference<T extends NotNullishValue>(value: T, areValuesEqual?: (a: T, b: T) => boolean): T;
|
|
41
|
+
|
|
42
|
+
export { usePreservedReference };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/hooks/usePreservedReference/usePreservedReference.ts
|
|
2
|
+
import { useMemo, useRef } from "react";
|
|
3
|
+
function usePreservedReference(value, areValuesEqual = areDeeplyEqual) {
|
|
4
|
+
const ref = useRef(value);
|
|
5
|
+
return useMemo(() => {
|
|
6
|
+
if (!areValuesEqual(ref.current, value)) {
|
|
7
|
+
ref.current = value;
|
|
8
|
+
}
|
|
9
|
+
return ref.current;
|
|
10
|
+
}, [areValuesEqual, value]);
|
|
11
|
+
}
|
|
12
|
+
function areDeeplyEqual(x, y) {
|
|
13
|
+
return JSON.stringify(x) === JSON.stringify(y);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
usePreservedReference
|
|
17
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description
|
|
3
|
+
* Returns the previous value of the input state.
|
|
4
|
+
* If a re-render occurs but the state value does not change, the previous value remains unchanged.
|
|
5
|
+
* If the state is an object or requires custom change detection, a `compare` function can be provided.
|
|
6
|
+
* By default, state changes are detected using `prev === next`.
|
|
7
|
+
*
|
|
8
|
+
* @template T - The type of the state.
|
|
9
|
+
* @param {T} state - The state whose previous value is to be tracked.
|
|
10
|
+
* @param {(prev: T | undefined, next: T) => boolean} [compare] - An optional comparison function to determine if the state has changed.
|
|
11
|
+
*
|
|
12
|
+
* @returns {T | undefined} The previous value of the state.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const [count, setCount] = useState(0);
|
|
16
|
+
* // initial value of previousCount is `0`
|
|
17
|
+
* const previousCount = usePrevious(count);
|
|
18
|
+
*/
|
|
19
|
+
declare function usePrevious<T>(state: T, compare?: (prev: T, next: T) => boolean): T;
|
|
20
|
+
|
|
21
|
+
export { usePrevious };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/hooks/usePrevious/usePrevious.ts
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
var strictEquals = (prev, next) => prev === next;
|
|
4
|
+
function usePrevious(state, compare = strictEquals) {
|
|
5
|
+
const prevRef = useRef(state);
|
|
6
|
+
const currentRef = useRef(state);
|
|
7
|
+
const isFirstRender = useRef(true);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
isFirstRender.current = false;
|
|
10
|
+
}, []);
|
|
11
|
+
if (isFirstRender.current || compare(currentRef.current, state)) {
|
|
12
|
+
return prevRef.current;
|
|
13
|
+
}
|
|
14
|
+
prevRef.current = currentRef.current;
|
|
15
|
+
currentRef.current = state;
|
|
16
|
+
return prevRef.current;
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
usePrevious
|
|
20
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DependencyList } from 'react';
|
|
2
|
+
|
|
3
|
+
type CleanupCallback = () => void;
|
|
4
|
+
/**
|
|
5
|
+
* @description
|
|
6
|
+
* `useRefEffect` is a custom hook that helps you set a reference to a specific DOM element and execute a callback whenever the element changes.
|
|
7
|
+
* This hook calls a cleanup function whenever the element changes to prevent memory leaks.
|
|
8
|
+
*
|
|
9
|
+
* @param callback - A callback function that is executed when the element is set. This function can return a cleanup function.
|
|
10
|
+
* @param deps - An array of dependencies that define when the callback should be re-executed. The `callback` is re-executed whenever the `deps` change.
|
|
11
|
+
*
|
|
12
|
+
* @returns A function to set the element. Pass this function to the `ref` attribute, and the `callback` will be called whenever the element changes.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import { useRefEffect } from 'react-simplikit';
|
|
16
|
+
*
|
|
17
|
+
* function Component() {
|
|
18
|
+
* const ref = useRefEffect<HTMLDivElement>(element => {
|
|
19
|
+
* console.log('Element mounted:', element);
|
|
20
|
+
*
|
|
21
|
+
* return () => {
|
|
22
|
+
* console.log('Element unmounted:', element);
|
|
23
|
+
* };
|
|
24
|
+
* }, []);
|
|
25
|
+
*
|
|
26
|
+
* return <div ref={ref}>Basic Example</div>;
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
declare function useRefEffect<Element extends HTMLElement = HTMLElement>(callback: (element: Element) => CleanupCallback | void, deps: DependencyList): (element: Element | null) => void;
|
|
30
|
+
|
|
31
|
+
export { useRefEffect };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/hooks/useRefEffect/useRefEffect.ts
|
|
2
|
+
import { useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/hooks/usePreservedCallback/usePreservedCallback.ts
|
|
5
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
6
|
+
function usePreservedCallback(callback) {
|
|
7
|
+
const callbackRef = useRef(callback);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
callbackRef.current = callback;
|
|
10
|
+
}, [callback]);
|
|
11
|
+
return useCallback((...args) => {
|
|
12
|
+
return callbackRef.current(...args);
|
|
13
|
+
}, []);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/hooks/useRefEffect/useRefEffect.ts
|
|
17
|
+
function useRefEffect(callback, deps) {
|
|
18
|
+
const preservedCallback = usePreservedCallback(callback);
|
|
19
|
+
const cleanupCallbackRef = useRef2(() => {
|
|
20
|
+
});
|
|
21
|
+
const effect = useCallback2(
|
|
22
|
+
(element) => {
|
|
23
|
+
cleanupCallbackRef.current();
|
|
24
|
+
cleanupCallbackRef.current = () => {
|
|
25
|
+
};
|
|
26
|
+
if (element == null) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const cleanup = preservedCallback(element);
|
|
30
|
+
if (typeof cleanup === "function") {
|
|
31
|
+
cleanupCallbackRef.current = cleanup;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
|
+
[preservedCallback, ...deps]
|
|
36
|
+
);
|
|
37
|
+
return effect;
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
useRefEffect
|
|
41
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SetStateAction } from 'react';
|
|
2
|
+
|
|
3
|
+
type Storage = {
|
|
4
|
+
get(key: string): string | null;
|
|
5
|
+
set(key: string, value: string): void;
|
|
6
|
+
remove(key: string): void;
|
|
7
|
+
clear(): void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type ToPrimitive<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : never;
|
|
11
|
+
type ToObject<T> = T extends unknown[] | Record<string, unknown> ? T : never;
|
|
12
|
+
type Serializable<T> = T extends string | number | boolean ? ToPrimitive<T> : ToObject<T>;
|
|
13
|
+
type StorageStateOptions<T> = {
|
|
14
|
+
storage?: Storage;
|
|
15
|
+
defaultValue?: Serializable<T>;
|
|
16
|
+
};
|
|
17
|
+
type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
|
|
18
|
+
defaultValue: Serializable<T>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* @description
|
|
22
|
+
* A React hook that functions like `useState` but persists the state value in browser storage.
|
|
23
|
+
* The value is retained across page reloads and can be shared between tabs when using `localStorage`.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} key - The key used to store the value in storage.
|
|
26
|
+
* @param {object} [options] - Configuration options for storage behavior.
|
|
27
|
+
* @param {Storage} [options.storage=localStorage] - The storage type (`localStorage` or `sessionStorage`). Defaults to `localStorage`.
|
|
28
|
+
* @param {T} [options.defaultValue] - The initial value if no existing value is found.
|
|
29
|
+
*
|
|
30
|
+
* @returns {[Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void]} A tuple:
|
|
31
|
+
* - `state`: The current state value retrieved from storage.
|
|
32
|
+
* - `setState`: A function to update and persist the state.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Counter with persistent state
|
|
36
|
+
* import { useStorageState } from 'react-simplikit';
|
|
37
|
+
*
|
|
38
|
+
* function Counter() {
|
|
39
|
+
* const [count, setCount] = useStorageState<number>('counter', {
|
|
40
|
+
* defaultValue: 0,
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* return <button onClick={() => setCount(prev => prev + 1)}>Count: {count}</button>;
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
declare function useStorageState<T>(key: string): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void];
|
|
47
|
+
declare function useStorageState<T>(key: string, { storage, defaultValue }: StorageStateOptionsWithDefaultValue<T>): readonly [Serializable<T>, (value: SetStateAction<Serializable<T>>) => void];
|
|
48
|
+
declare function useStorageState<T>(key: string, { storage, defaultValue }: StorageStateOptions<T>): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void];
|
|
49
|
+
|
|
50
|
+
export { type Serializable, useStorageState };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// src/hooks/useStorageState/useStorageState.ts
|
|
2
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
|
|
4
|
+
// src/hooks/useStorageState/storage.ts
|
|
5
|
+
var MemoStorage = class {
|
|
6
|
+
storage = /* @__PURE__ */ new Map();
|
|
7
|
+
get(key) {
|
|
8
|
+
return this.storage.get(key) ?? null;
|
|
9
|
+
}
|
|
10
|
+
set(key, value) {
|
|
11
|
+
this.storage.set(key, value);
|
|
12
|
+
}
|
|
13
|
+
remove(key) {
|
|
14
|
+
this.storage.delete(key);
|
|
15
|
+
}
|
|
16
|
+
clear() {
|
|
17
|
+
this.storage.clear();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var LocalStorage = class {
|
|
21
|
+
static canUse() {
|
|
22
|
+
const TEST_KEY = generateTestKey();
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(TEST_KEY, "test");
|
|
25
|
+
localStorage.removeItem(TEST_KEY);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
get(key) {
|
|
32
|
+
return localStorage.getItem(key);
|
|
33
|
+
}
|
|
34
|
+
set(key, value) {
|
|
35
|
+
localStorage.setItem(key, value);
|
|
36
|
+
}
|
|
37
|
+
remove(key) {
|
|
38
|
+
localStorage.removeItem(key);
|
|
39
|
+
}
|
|
40
|
+
clear() {
|
|
41
|
+
localStorage.clear();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var SessionStorage = class {
|
|
45
|
+
static canUse() {
|
|
46
|
+
const TEST_KEY = generateTestKey();
|
|
47
|
+
try {
|
|
48
|
+
sessionStorage.setItem(TEST_KEY, "test");
|
|
49
|
+
sessionStorage.removeItem(TEST_KEY);
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
get(key) {
|
|
56
|
+
return sessionStorage.getItem(key);
|
|
57
|
+
}
|
|
58
|
+
set(key, value) {
|
|
59
|
+
sessionStorage.setItem(key, value);
|
|
60
|
+
}
|
|
61
|
+
remove(key) {
|
|
62
|
+
sessionStorage.removeItem(key);
|
|
63
|
+
}
|
|
64
|
+
clear() {
|
|
65
|
+
sessionStorage.clear();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function generateTestKey() {
|
|
69
|
+
return new Array(4).fill(null).map(() => Math.random().toString(36).slice(2)).join("");
|
|
70
|
+
}
|
|
71
|
+
function generateStorage() {
|
|
72
|
+
if (LocalStorage.canUse()) {
|
|
73
|
+
return new LocalStorage();
|
|
74
|
+
}
|
|
75
|
+
return new MemoStorage();
|
|
76
|
+
}
|
|
77
|
+
function generateSessionStorage() {
|
|
78
|
+
if (SessionStorage.canUse()) {
|
|
79
|
+
return new SessionStorage();
|
|
80
|
+
}
|
|
81
|
+
return new MemoStorage();
|
|
82
|
+
}
|
|
83
|
+
var safeLocalStorage = generateStorage();
|
|
84
|
+
var safeSessionStorage = generateSessionStorage();
|
|
85
|
+
|
|
86
|
+
// src/hooks/useStorageState/useStorageState.ts
|
|
87
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
88
|
+
var emitListeners = () => {
|
|
89
|
+
listeners.forEach((listener) => listener());
|
|
90
|
+
};
|
|
91
|
+
function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {}) {
|
|
92
|
+
const cache = useRef({
|
|
93
|
+
data: null,
|
|
94
|
+
parsed: defaultValue
|
|
95
|
+
});
|
|
96
|
+
const getSnapshot = useCallback(() => {
|
|
97
|
+
const data = storage.get(key);
|
|
98
|
+
if (data !== cache.current.data) {
|
|
99
|
+
try {
|
|
100
|
+
cache.current.parsed = data != null ? JSON.parse(data) : defaultValue;
|
|
101
|
+
} catch {
|
|
102
|
+
cache.current.parsed = defaultValue;
|
|
103
|
+
}
|
|
104
|
+
cache.current.data = data;
|
|
105
|
+
}
|
|
106
|
+
return cache.current.parsed;
|
|
107
|
+
}, [defaultValue, key, storage]);
|
|
108
|
+
const storageState = useSyncExternalStore(
|
|
109
|
+
(onStoreChange) => {
|
|
110
|
+
listeners.add(onStoreChange);
|
|
111
|
+
const handler = (event) => {
|
|
112
|
+
if (event.key === key) {
|
|
113
|
+
onStoreChange();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
window.addEventListener("storage", handler);
|
|
117
|
+
return () => {
|
|
118
|
+
listeners.delete(onStoreChange);
|
|
119
|
+
window.removeEventListener("storage", handler);
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
() => getSnapshot(),
|
|
123
|
+
() => defaultValue
|
|
124
|
+
);
|
|
125
|
+
const setStorageState = useCallback(
|
|
126
|
+
(value) => {
|
|
127
|
+
const nextValue = typeof value === "function" ? value(getSnapshot()) : value;
|
|
128
|
+
if (nextValue == null) {
|
|
129
|
+
storage.remove(key);
|
|
130
|
+
} else {
|
|
131
|
+
storage.set(key, JSON.stringify(nextValue));
|
|
132
|
+
}
|
|
133
|
+
emitListeners();
|
|
134
|
+
},
|
|
135
|
+
[getSnapshot, key, storage]
|
|
136
|
+
);
|
|
137
|
+
return [storageState, setStorageState];
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
useStorageState
|
|
141
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
type ThrottleOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
|
|
4
|
+
* If `edges` includes "leading", the function will be invoked at the start of the delay period.
|
|
5
|
+
* If `edges` includes "trailing", the function will be invoked at the end of the delay period.
|
|
6
|
+
* If both "leading" and "trailing" are included, the function will be invoked at both the start and end of the delay period.
|
|
7
|
+
* @default ["leading", "trailing"]
|
|
8
|
+
*/
|
|
9
|
+
edges?: Array<'leading' | 'trailing'>;
|
|
10
|
+
};
|
|
11
|
+
type ThrottledFunction<F extends (...args: any[]) => void> = {
|
|
12
|
+
(...args: Parameters<F>): void;
|
|
13
|
+
cancel: () => void;
|
|
14
|
+
};
|
|
15
|
+
declare function throttle<F extends (...args: any[]) => void>(func: F, throttleMs: number, { edges }?: ThrottleOptions): ThrottledFunction<F>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @description
|
|
19
|
+
* A React hook that creates a throttled version of a callback function.
|
|
20
|
+
* This is useful for limiting the rate at which a function can be called,
|
|
21
|
+
* such as when handling scroll or resize events.
|
|
22
|
+
*
|
|
23
|
+
* @template F - The type of the callback function.
|
|
24
|
+
* @param {F} callback - The function to be throttled.
|
|
25
|
+
* @param {number} wait - The number of milliseconds to throttle invocations to.
|
|
26
|
+
* @param {{ edges?: Array<'leading' | 'trailing'> }} [options] - Options to control the behavior of the throttle.
|
|
27
|
+
* @returns {F & { cancel: () => void }} - Returns the throttled function with a `cancel` method to cancel pending executions.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const throttledScroll = useThrottle(() => {
|
|
31
|
+
* console.log('Scroll event');
|
|
32
|
+
* }, 200, { edges: ['leading', 'trailing'] });
|
|
33
|
+
*
|
|
34
|
+
* useEffect(() => {
|
|
35
|
+
* window.addEventListener('scroll', throttledScroll);
|
|
36
|
+
* return () => {
|
|
37
|
+
* window.removeEventListener('scroll', throttledScroll);
|
|
38
|
+
* throttledScroll.cancel();
|
|
39
|
+
* };
|
|
40
|
+
* }, [throttledScroll]);
|
|
41
|
+
*/
|
|
42
|
+
declare function useThrottle<F extends (...args: any[]) => any>(callback: F, wait: number, options?: Parameters<typeof throttle>[2]): {
|
|
43
|
+
(...args: any[]): void;
|
|
44
|
+
cancel: () => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { useThrottle };
|