async-fetch 0.3.7 → 0.3.8

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/README.md CHANGED
@@ -51,18 +51,18 @@ The minimum requirement for the hook is a url string as the first argument. The
51
51
 
52
52
  | Key | Type | Definition | Default |
53
53
  | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- | ------- |
54
- | initialPending | Boolean | Initial state for the pending constant. | |
55
- | initialData | Any | Initial state for the data constant. | |
56
- | initialError | Any | Initial state for the error constant. | |
57
- | deps | Array | List of dependencies to run the request on. | |
54
+ | initialPending | Boolean | Initial state for the pending constant. | false |
55
+ | initialError | E or Any | Initial state for the error constant. | |
56
+ | initialData | T or Any | Initial state for the data constant. | |
57
+ | auto | Boolean | Whether or not the request should send on mount. | true |
58
58
  | poll | Number | Number of milliseconds to wait for polling requests. | |
59
59
  | timeout | Number | Number of milliseconds to wait before canceling the request. | 30000 |
60
- | ignoreCleanup | Boolean | Whether or not the hook should cleanup on component unmount. | |
61
60
  | ignoreRequest | Boolean | Whether or not the request should send. | |
61
+ | ignoreCleanup | Boolean | Whether or not the hook should cleanup on component unmount. | |
62
62
  | query | Object | JSON object to append to the url as search params. | |
63
63
  | params | Object | JSON object to append to the url as search params. | |
64
- | data | Object | JSON object to send in the request body. | |
65
- | parser | String | Method used to parse the response. | "json" |
64
+ | data | Any | JSON object to send in the request body. | |
65
+ | parser | String | Method used to parse the response. Options: json, text, blob, formData, arrayBuffer. | "json" |
66
66
  | onStart | Function | Callback function to call when the request starts. | |
67
67
  | onSuccess | Function | Callback function to call when the response has been received. The response is available as the first argument. | |
68
68
  | onFail | Function | Callback function to call when the request has failed. The error is available as the first argument. | |
@@ -73,7 +73,7 @@ The minimum requirement for the hook is a url string as the first argument. The
73
73
  | Key | Type | Definition |
74
74
  | ------------- | -------- | ---------------------------------------- |
75
75
  | pending | Boolean | Whether or not the request is active. |
76
- | error | Any | The response error. |
77
- | data | Any | The response data. |
76
+ | error | E or Any | The response error. |
77
+ | data | T or Any | The response data. |
78
78
  | sendRequest | Function | Function to send the request manually. |
79
79
  | cancelRequest | Function | Function to cancel the request manually. |
package/dist/index.d.ts CHANGED
@@ -1,30 +1,3 @@
1
- interface RequestProps {
2
- initialPending?: boolean;
3
- initialData?: any;
4
- initialError?: any;
5
- deps?: string[];
6
- poll?: number;
7
- timeout?: number;
8
- ignoreRequest?: boolean;
9
- ignoreCleanup?: boolean;
10
- query?: any;
11
- params?: any;
12
- data?: any;
13
- parser?: "json" | "text" | "blob" | "formData" | "arrayBuffer";
14
- onStart?: () => void;
15
- onSuccess?: (data: any) => void;
16
- onFail?: (error: any) => void;
17
- onFinish?: () => void;
18
- headers?: Record<string, string>;
19
- body?: any;
20
- signal?: AbortSignal;
21
- }
22
- interface ResponseProps {
23
- pending?: boolean;
24
- data?: any;
25
- error?: any;
26
- sendRequest: () => void;
27
- cancelRequest: () => void;
28
- }
29
- declare function useAsyncFetch(stringUrl: string, props?: RequestProps): ResponseProps;
1
+ import { FetchError, RequestProps, ResponseProps } from "./interfaces";
2
+ declare function useAsyncFetch<T = any, E = FetchError>(urlString: string, props?: RequestProps<T, E>): ResponseProps<T, E>;
30
3
  export default useAsyncFetch;
package/dist/index.js CHANGED
@@ -1,93 +1,85 @@
1
- import { useState, useCallback, useEffect } from "react";
1
+ import { useState, useRef, useCallback, useEffect } from "react";
2
2
  import useInterval from "./useInterval.js";
3
- function useAsyncFetch(stringUrl, props = {}) {
4
- const { initialPending, initialData, initialError, deps = [], poll, timeout = 30000, // 30 seconds.
5
- ignoreRequest, ignoreCleanup, query, params, data: body, parser = "json", onStart, onSuccess, onFail, onFinish, ...fetchProps } = props;
3
+ import normalizeError from "./normalizeError.js";
4
+ function useAsyncFetch(urlString, props = {}) {
5
+ const { initialPending = false, initialError, initialData, auto = true, poll, timeout = 30000, ignoreRequest, ignoreCleanup, query, params, data: body, parser = "json", onStart, onSuccess, onFail, onFinish, ...fetchOptions } = props;
6
6
  const [pending, setPending] = useState(initialPending);
7
- const [pending2, setPending2] = useState(initialPending);
8
- const [data, setData] = useState(initialData);
9
7
  const [error, setError] = useState(initialError);
10
- const [cancelSource, setCancelSource] = useState();
8
+ const [data, setData] = useState(initialData);
9
+ const fetchOptionsRef = useRef(fetchOptions);
10
+ const controllerRef = useRef(null);
11
+ const requestIdRef = useRef(0);
12
+ fetchOptionsRef.current = fetchOptions;
11
13
  const cancelRequest = useCallback(() => {
12
- if (cancelSource?.abort)
13
- cancelSource.abort();
14
- }, [cancelSource]);
14
+ controllerRef.current?.abort();
15
+ controllerRef.current = null;
16
+ }, []);
15
17
  const sendRequest = useCallback(async () => {
16
18
  if (ignoreRequest === true)
17
19
  return;
18
- const url = new URL(stringUrl, window.location.origin);
19
- if (query || params) {
20
- url.search = new URLSearchParams(query || params || {}).toString();
21
- }
22
- const contentType = fetchProps.headers?.["Content-Type"] ||
23
- fetchProps.headers?.["content-type"];
24
- if (contentType === "application/x-www-form-urlencoded") {
25
- fetchProps.body = new URLSearchParams(body || {});
26
- }
27
- else if (body) {
28
- fetchProps.body = JSON.stringify(body);
29
- }
30
- const controller = new AbortController();
31
- const requestTimeout = setTimeout(() => controller.abort(), timeout);
32
- fetchProps.signal = controller.signal;
33
- if (pending)
34
- setPending2(true);
35
- if (!pending)
36
- setPending(true);
37
- setError(undefined);
38
20
  cancelRequest();
39
- setCancelSource(controller);
21
+ setPending(true);
22
+ setError(undefined);
40
23
  if (onStart)
41
24
  onStart();
25
+ const controller = new AbortController();
26
+ controllerRef.current = controller;
27
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
28
+ const requestId = ++requestIdRef.current;
42
29
  try {
43
- const response = await fetch(url, fetchProps);
30
+ const url = new URL(urlString, window.location.origin);
31
+ url.search = new URLSearchParams({ ...query, ...params }).toString();
32
+ const headers = new Headers(fetchOptionsRef.current.headers);
33
+ let resolvedBody;
34
+ if (headers.get("content-type") === "application/x-www-form-urlencoded") {
35
+ resolvedBody = new URLSearchParams(body ?? {});
36
+ }
37
+ else if (body instanceof FormData || body instanceof Blob) {
38
+ resolvedBody = body;
39
+ }
40
+ else if (body !== undefined) {
41
+ resolvedBody = JSON.stringify(body);
42
+ }
43
+ const response = await fetch(url, {
44
+ ...fetchOptionsRef.current,
45
+ headers,
46
+ body: resolvedBody ?? fetchOptionsRef.current.body,
47
+ signal: controller.signal,
48
+ });
44
49
  if (!response.ok) {
45
- throw new Error(JSON.stringify({
46
- code: response.status,
47
- text: response.statusText,
50
+ throw {
51
+ status: response.status,
52
+ statusText: response.statusText,
48
53
  response: await response.text(),
49
- }));
50
- }
51
- else {
52
- const parsedResponse = await response[parser]();
53
- setData(parsedResponse);
54
- if (onSuccess)
55
- onSuccess(parsedResponse);
54
+ };
56
55
  }
56
+ const parsedResponse = (await response[parser]());
57
+ setData(parsedResponse);
58
+ onSuccess?.(parsedResponse);
57
59
  }
58
- catch (e) {
59
- if (e.name !== "AbortError") {
60
- let error;
61
- try {
62
- error = JSON.parse(e.toString().replace("Error:", "").trim());
63
- }
64
- catch {
65
- error = { response: e.toString(), text: e.toString() };
66
- }
67
- setError(error);
68
- if (onFail)
69
- onFail(error);
70
- }
60
+ catch (err) {
61
+ if (err?.name === "AbortError")
62
+ return;
63
+ const normalized = normalizeError(err);
64
+ setError(normalized);
65
+ onFail?.(normalized);
71
66
  }
72
67
  finally {
73
- clearTimeout(requestTimeout);
74
- if (pending) {
75
- setPending2(undefined);
76
- }
77
- else {
78
- setPending(undefined);
68
+ clearTimeout(timeoutId);
69
+ if (requestId === requestIdRef.current) {
70
+ setPending(false);
71
+ if (onFinish)
72
+ onFinish();
79
73
  }
80
- if (onFinish)
81
- onFinish();
82
74
  }
83
75
  }, [
84
76
  ignoreRequest,
85
- stringUrl,
77
+ cancelRequest,
78
+ timeout,
79
+ urlString,
86
80
  query,
87
81
  params,
88
- fetchProps,
89
82
  body,
90
- timeout,
91
83
  parser,
92
84
  onStart,
93
85
  onSuccess,
@@ -95,8 +87,9 @@ function useAsyncFetch(stringUrl, props = {}) {
95
87
  onFinish,
96
88
  ]);
97
89
  useEffect(() => {
98
- sendRequest();
99
- }, deps);
90
+ if (auto)
91
+ sendRequest();
92
+ }, [auto, sendRequest]);
100
93
  useInterval(() => {
101
94
  sendRequest();
102
95
  }, poll);
@@ -105,13 +98,13 @@ function useAsyncFetch(stringUrl, props = {}) {
105
98
  if (ignoreCleanup !== true)
106
99
  cancelRequest();
107
100
  };
108
- }, []);
101
+ }, [ignoreCleanup, cancelRequest]);
109
102
  return {
110
- pending: pending || pending2,
111
- data,
103
+ pending,
112
104
  error,
113
- sendRequest,
105
+ data,
114
106
  cancelRequest,
107
+ sendRequest,
115
108
  };
116
109
  }
117
110
  export default useAsyncFetch;
@@ -0,0 +1,30 @@
1
+ export interface FetchError {
2
+ status: number;
3
+ statusText: string;
4
+ response: string;
5
+ }
6
+ export interface RequestProps<T, E> extends RequestInit {
7
+ initialPending?: boolean;
8
+ initialError?: E;
9
+ initialData?: T;
10
+ auto?: boolean;
11
+ poll?: number;
12
+ timeout?: number;
13
+ ignoreRequest?: boolean;
14
+ ignoreCleanup?: boolean;
15
+ query?: Record<string, any>;
16
+ params?: Record<string, any>;
17
+ data?: any;
18
+ parser?: "json" | "text" | "blob" | "formData" | "arrayBuffer";
19
+ onStart?: () => void;
20
+ onSuccess?: (data: T) => void;
21
+ onFail?: (error: E) => void;
22
+ onFinish?: () => void;
23
+ }
24
+ export interface ResponseProps<T, E> {
25
+ pending: boolean;
26
+ error?: E;
27
+ data?: T;
28
+ sendRequest: () => Promise<void>;
29
+ cancelRequest: () => void;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { FetchError } from "./interfaces";
2
+ declare function normalizeError<E = FetchError>(err: unknown): E;
3
+ export default normalizeError;
@@ -0,0 +1,22 @@
1
+ function normalizeError(err) {
2
+ if (err instanceof Error) {
3
+ return {
4
+ status: 500,
5
+ statusText: err.message,
6
+ response: err.stack ?? "",
7
+ };
8
+ }
9
+ if (typeof err === "object" && err !== null) {
10
+ return {
11
+ status: err.status ?? 500,
12
+ statusText: err.statusText ?? "",
13
+ response: err.response ?? "",
14
+ };
15
+ }
16
+ return {
17
+ status: 500,
18
+ statusText: String(err),
19
+ response: "",
20
+ };
21
+ }
22
+ export default normalizeError;
@@ -1,16 +1,16 @@
1
1
  import { useRef, useEffect } from "react";
2
2
  function useInterval(callback, poll) {
3
- const callbackRef = useRef(() => { }); // noop
3
+ const callbackRef = useRef(() => { });
4
4
  useEffect(() => {
5
- if (typeof callback === "function") {
6
- callbackRef.current = callback;
7
- }
5
+ callbackRef.current = callback;
8
6
  }, [callback]);
9
7
  useEffect(() => {
10
- if (typeof poll !== "number" || poll < 100)
8
+ if (!Number.isInteger(poll))
11
9
  return;
12
- const interval = setInterval(() => callbackRef.current(), poll);
13
- return () => clearInterval(interval);
10
+ const id = setInterval(() => {
11
+ callbackRef.current?.();
12
+ }, poll);
13
+ return () => clearInterval(id);
14
14
  }, [poll]);
15
15
  }
16
16
  export default useInterval;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "async-fetch",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Use async fetch hook for requests within React components.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
8
8
  "scripts": {
9
- "build": "tsc",
9
+ "build": "npx rimraf dist && tsc",
10
10
  "test-esm": "yarn build && node test/test-esm.mjs"
11
11
  },
12
12
  "repository": {
@@ -26,10 +26,10 @@
26
26
  },
27
27
  "homepage": "https://github.com/nameer-rizvi/useAsyncFetch#readme",
28
28
  "devDependencies": {
29
- "@types/node": "^22.7.4",
30
- "@types/react": "^18.3.11",
31
- "react": "^18.3.1",
29
+ "@types/node": "^25.0.3",
30
+ "@types/react": "^19.2.7",
31
+ "react": "^19.2.3",
32
32
  "ts-node": "^10.9.2",
33
- "typescript": "^5.6.2"
33
+ "typescript": "^5.9.3"
34
34
  }
35
35
  }