@sohanemon/utils 5.2.2 → 5.2.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.
@@ -1,5 +1,5 @@
1
1
  export { Icon as Iconify } from '@iconify/react';
2
- export * from './html-injector';
3
- export * from './media-wrapper';
2
+ export { HtmlInjector } from './html-injector';
3
+ export { MediaWrapper } from './media-wrapper';
4
4
  export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
5
- export * from './scrollable-marker';
5
+ export { ScrollableMarker } from './scrollable-marker';
@@ -1,6 +1,7 @@
1
1
  'use client';
2
+ //NOTE: It's currently unsupported to use "export *" in a client boundary
2
3
  export { Icon as Iconify } from '@iconify/react';
3
- export * from './html-injector';
4
- export * from './media-wrapper';
4
+ export { HtmlInjector } from './html-injector';
5
+ export { MediaWrapper } from './media-wrapper';
5
6
  export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
6
- export * from './scrollable-marker';
7
+ export { ScrollableMarker } from './scrollable-marker';
@@ -1,17 +1,30 @@
1
- interface UseAsyncOptions<T extends (...args: any) => any> {
2
- initialArgs?: Parameters<T>[0];
3
- callback?: {
4
- onSuccess?: (result: T) => void;
5
- onError?: (error: Error) => void;
6
- onExecute?: () => void;
7
- onSettle?: () => void;
8
- };
9
- mode?: 'onLoad' | 'onTrigger';
1
+ import * as React from 'react';
2
+ type AsyncStatus = 'idle' | 'pending' | 'success' | 'error';
3
+ type OnSuccess<TData> = (data: TData) => void | Promise<void>;
4
+ type OnError<TError extends Error = Error> = (error: TError) => void | Promise<void>;
5
+ type OnSettled<TData, TError extends Error = Error> = (data: TData | undefined, error: TError | undefined) => void | Promise<void>;
6
+ interface UseAsyncOptions<TData = unknown, TError extends Error = Error> {
7
+ mode?: 'auto' | 'manual';
8
+ deps?: React.DependencyList;
9
+ onSuccess?: OnSuccess<TData>;
10
+ onError?: OnError<TError>;
11
+ onSettled?: OnSettled<TData, TError>;
10
12
  }
11
- export declare const useAsync: <T extends (...args: any) => any>(fn: T, opts?: UseAsyncOptions<T>) => {
12
- execute: (args: Parameters<T>[0]) => Promise<void>;
13
- isLoading: boolean;
14
- result: Awaited<ReturnType<T>>;
15
- error: Error;
16
- };
13
+ interface UseAsyncReturn<TData, TError extends Error = Error> {
14
+ data: TData | undefined;
15
+ error: TError | undefined;
16
+ status: AsyncStatus;
17
+ isIdle: boolean;
18
+ isPending: boolean;
19
+ isSuccess: boolean;
20
+ isError: boolean;
21
+ execute: () => Promise<TData>;
22
+ }
23
+ /**
24
+ * A fully typesafe async hook inspired by TanStack Query
25
+ * @param asyncFn - Async function that returns data
26
+ * @param options - Configuration options including callbacks
27
+ * @returns Object with data, error, status and helper methods
28
+ */
29
+ export declare function useAsync<TData, TError extends Error = Error>(asyncFn: () => Promise<TData>, options?: UseAsyncOptions<TData, TError>): UseAsyncReturn<TData, TError>;
17
30
  export {};
@@ -1,44 +1,72 @@
1
- import { useCallback, useState, useTransition } from 'react';
2
- import { useIsomorphicEffect } from '.';
3
- export const useAsync = (fn, opts = {}) => {
4
- const { initialArgs, callback = {}, mode = 'onTrigger' } = opts;
5
- const { onSuccess, onError, onExecute, onSettle } = callback;
6
- const [isLoading, setIsLoading] = useState(false);
7
- const [result, setValue] = useState(null);
8
- const [error, setError] = useState(null);
9
- const [isPending, startTransition] = useTransition();
10
- const execute = useCallback(async (args) => {
11
- setIsLoading(true);
12
- setValue(null);
13
- setError(null);
14
- onExecute?.();
1
+ 'use client';
2
+ import * as React from 'react';
3
+ /**
4
+ * A fully typesafe async hook inspired by TanStack Query
5
+ * @param asyncFn - Async function that returns data
6
+ * @param options - Configuration options including callbacks
7
+ * @returns Object with data, error, status and helper methods
8
+ */
9
+ export function useAsync(asyncFn, options = {}) {
10
+ const { mode = 'manual', deps, onSuccess, onError, onSettled } = options;
11
+ const [data, setData] = React.useState(undefined);
12
+ const [error, setError] = React.useState(undefined);
13
+ const [status, setStatus] = React.useState('idle');
14
+ const isMountedRef = React.useRef(true);
15
+ const memoizedOnSuccess = React.useCallback(onSuccess || (() => { }), [
16
+ onSuccess,
17
+ ]);
18
+ const memoizedOnError = React.useCallback(onError || (() => { }), [onError]);
19
+ const memoizedOnSettled = React.useCallback(onSettled || (() => { }), [
20
+ onSettled,
21
+ ]);
22
+ const execute = React.useCallback(async () => {
23
+ setStatus('pending');
24
+ setError(undefined);
15
25
  try {
16
- startTransition(() => {
17
- (async () => {
18
- const response = await fn(args);
19
- setValue(response);
20
- onSuccess?.(response);
21
- })();
22
- });
26
+ const result = await asyncFn();
27
+ if (isMountedRef.current) {
28
+ setData(result);
29
+ setStatus('success');
30
+ await memoizedOnSuccess(result);
31
+ await memoizedOnSettled(result, undefined);
32
+ }
33
+ return result;
23
34
  }
24
- catch (error) {
25
- setError(error);
26
- onError?.(error);
35
+ catch (err) {
36
+ const error = err instanceof Error ? err : new Error(String(err));
37
+ const typedError = error;
38
+ if (isMountedRef.current) {
39
+ setError(typedError);
40
+ setStatus('error');
41
+ await memoizedOnError(typedError);
42
+ await memoizedOnSettled(undefined, typedError);
43
+ }
44
+ throw error;
27
45
  }
28
- finally {
29
- setIsLoading(false);
30
- onSettle?.();
31
- }
32
- }, [fn, onExecute, onSuccess, onError, onSettle]);
33
- useIsomorphicEffect(() => {
34
- if (mode === 'onLoad') {
35
- execute(initialArgs);
46
+ }, [asyncFn, memoizedOnSuccess, memoizedOnError, memoizedOnSettled]);
47
+ React.useEffect(() => {
48
+ if (mode === 'auto') {
49
+ execute();
36
50
  }
51
+ }, [asyncFn, mode, execute]);
52
+ React.useEffect(() => {
53
+ return () => {
54
+ isMountedRef.current = false;
55
+ };
37
56
  }, []);
57
+ React.useEffect(() => {
58
+ if (deps && mode === 'auto') {
59
+ execute();
60
+ }
61
+ }, deps ? deps : []);
38
62
  return {
39
- execute,
40
- isLoading: isLoading || isPending,
41
- result,
63
+ data,
42
64
  error,
65
+ status,
66
+ isIdle: status === 'idle',
67
+ isPending: status === 'pending',
68
+ isSuccess: status === 'success',
69
+ isError: status === 'error',
70
+ execute,
43
71
  };
44
- };
72
+ }
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  export * from './action';
3
+ export * from './async';
3
4
  export * from './schedule';
4
5
  /**
5
6
  * Hook to detect clicks outside of a referenced element.
@@ -2,6 +2,7 @@
2
2
  import * as React from 'react';
3
3
  import { copyToClipboard } from '../functions';
4
4
  export * from './action';
5
+ export * from './async';
5
6
  export * from './schedule';
6
7
  /**
7
8
  * Hook to detect clicks outside of a referenced element.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sohanemon/utils",
3
- "version": "5.2.2",
3
+ "version": "5.2.4",
4
4
  "author": "Sohan Emon <sohanemon@outlook.com>",
5
5
  "description": "",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- export declare function ScrollableMarker(): any;
@@ -1,56 +0,0 @@
1
- 'use client';
2
- import { useScheduledEffect } from '../hooks/schedule';
3
- export function ScrollableMarker() {
4
- useScheduledEffect(() => {
5
- const root = document.body;
6
- if (!root)
7
- return;
8
- const isScrollable = (el) => {
9
- const style = getComputedStyle(el);
10
- if (style.overflow === 'hidden' &&
11
- style.overflowY === 'hidden' &&
12
- style.overflowX === 'hidden')
13
- return false;
14
- const canScrollY = (style.overflowY === 'auto' || style.overflowY === 'scroll') &&
15
- el.scrollHeight > el.clientHeight;
16
- const canScrollX = (style.overflowX === 'auto' || style.overflowX === 'scroll') &&
17
- el.scrollWidth > el.clientWidth;
18
- return canScrollY || canScrollX;
19
- };
20
- const markScrollable = (el) => {
21
- if (isScrollable(el))
22
- el.dataset.scrollable = 'true';
23
- else
24
- delete el.dataset.scrollable;
25
- };
26
- const scanTree = (node) => {
27
- markScrollable(node);
28
- for (let i = 0; i < node.children.length; i++) {
29
- const child = node.children[i];
30
- scanTree(child);
31
- }
32
- };
33
- requestIdleCallback(() => scanTree(root));
34
- const observer = new MutationObserver((mutations) => {
35
- for (const m of mutations) {
36
- if (m.type === 'childList') {
37
- m.addedNodes.forEach((n) => {
38
- if (n instanceof HTMLElement)
39
- scanTree(n);
40
- });
41
- }
42
- else if (m.type === 'attributes' && m.target instanceof HTMLElement) {
43
- markScrollable(m.target);
44
- }
45
- }
46
- });
47
- observer.observe(root, {
48
- subtree: true,
49
- childList: true,
50
- attributes: true,
51
- attributeFilter: ['style', 'class'],
52
- });
53
- return () => observer.disconnect();
54
- }, []);
55
- return null;
56
- }