cod-smart-fetch 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 COD Innovations
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # use-smart-fetch
2
+
3
+ A professional, type-safe, and feature-rich React hook for data fetching. It simplifies network requests by handling loading states, errors, data caching, debouncing, and more, all while providing a modern API for both automatic and manual execution.
4
+
5
+ ## Features
6
+
7
+ - **Built-in State Management**: Automatically handles `isLoading`, `error`, and `data` states.
8
+ - **Auto-Fetch**: Trigger requests immediately when a component mounts or dependencies change.
9
+ - **Debounce Support**: Native support for debounced requests (perfect for search inputs).
10
+ - **Global Configuration**: Set base URLs, default headers, and interceptors once for your entire app.
11
+ - **Interceptors**: Powerful request and response interception for logging, auth tokens, or error handling.
12
+ - **Smart File Uploads**: Simplified API for uploading files and `FormData`.
13
+ - **TypeScript First**: Written in TypeScript with full type inference for request bodies and responses.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install use-smart-fetch
19
+ # or
20
+ yarn add use-smart-fetch
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Automatic Fetching (GET)
26
+ Ideal for loading data when a component mounts.
27
+
28
+ ```tsx
29
+ import { useSmartFetch } from 'use-smart-fetch';
30
+
31
+ interface User {
32
+ id: number;
33
+ name: string;
34
+ }
35
+
36
+ const UserProfile = () => {
37
+ const { data, isLoading, error } = useSmartFetch<User>({
38
+ url: 'https://jsonplaceholder.typicode.com/users/1',
39
+ autoFetch: true
40
+ });
41
+
42
+ if (isLoading) return <p>Loading...</p>;
43
+ if (error) return <p>Error: {error.message}</p>;
44
+
45
+ return <div>User: {data?.name}</div>;
46
+ };
47
+ ```
48
+
49
+ ### 2. Manual Execution (POST)
50
+ Ideal for form submissions or actions triggered by user interaction.
51
+
52
+ ```tsx
53
+ import { useSmartFetch } from 'use-smart-fetch';
54
+
55
+ const CreatePost = () => {
56
+ const { post, isLoading } = useSmartFetch();
57
+
58
+ const handleSubmit = async () => {
59
+ const response = await post('https://jsonplaceholder.typicode.com/posts', {
60
+ title: 'New Post',
61
+ body: 'This is the content'
62
+ });
63
+
64
+ if (response) {
65
+ alert('Post Created!');
66
+ }
67
+ };
68
+
69
+ return (
70
+ <button onClick={handleSubmit} disabled={isLoading}>
71
+ {isLoading ? 'Creating...' : 'Create Post'}
72
+ </button>
73
+ );
74
+ };
75
+ ```
76
+
77
+ ## Advanced Features
78
+
79
+ ### Global Configuration
80
+ You can configure a base URL and global headers (like Authorization tokens) using the `SmartFetchProvider`.
81
+
82
+ ```tsx
83
+ // app.tsx
84
+ import { SmartFetchProvider } from 'use-smart-fetch';
85
+
86
+ const App = () => (
87
+ <SmartFetchProvider config={{
88
+ baseUrl: 'https://api.my-app.com/v1',
89
+ headers: {
90
+ 'Authorization': 'Bearer my-token'
91
+ }
92
+ }}>
93
+ <MyComponent />
94
+ </SmartFetchProvider>
95
+ );
96
+ ```
97
+
98
+ ### Interceptors
99
+ Interceptors allow you to modify requests before they are sent or responses before they are processed. This is useful for logging logic or global error handling (e.g., redirecting to login on 401).
100
+
101
+ ```tsx
102
+ <SmartFetchProvider config={{
103
+ baseUrl: 'https://api.my-app.com',
104
+ interceptors: {
105
+ request: async (options) => {
106
+ console.log('Requesting:', options.method, options.url);
107
+ return options;
108
+ },
109
+ response: async (response) => {
110
+ if (response.status === 401) {
111
+ window.location.href = '/login';
112
+ }
113
+ return response;
114
+ }
115
+ }
116
+ }}>
117
+ <App />
118
+ </SmartFetchProvider>
119
+ ```
120
+
121
+ ### Debounced Requests (Search)
122
+ Easily implement "search-as-you-type" functionality without extra libraries.
123
+
124
+ ```tsx
125
+ const [search, setSearch] = useState('');
126
+ const { data } = useSmartFetch({
127
+ url: `/search?q=${search}`,
128
+ autoFetch: true,
129
+ debounce: 500 // Wait 500ms after user stops typing
130
+ });
131
+ ```
132
+
133
+ ### File Uploads
134
+ The `upload` helper automatically detects files and handles `FormData` conversion and headers for you.
135
+
136
+ ```tsx
137
+ const { upload } = useSmartFetch();
138
+
139
+ const handleUpload = async (file: File) => {
140
+ // Uploads as multipart/form-data
141
+ await upload('/avatars', file);
142
+ };
143
+ ```
144
+
145
+ ## API Documentation
146
+
147
+ ### `useSmartFetch<T>(options?: UseSmartFetchOptions)`
148
+
149
+ #### Configuration Options
150
+ | Property | Type | Default | Description |
151
+ |----------|------|---------|-------------|
152
+ | `url` | `string` | `undefined` | The endpoint URL. Required if `autoFetch` is true. |
153
+ | `method` | `'GET' \| 'POST' ...` | `'GET'` | The HTTP method to use. |
154
+ | `headers` | `HeadersInit` | `{}` | Custom headers. merged with global headers. |
155
+ | `body` | `any` | `undefined` | The request payload. |
156
+ | `autoFetch` | `boolean` | `false` | If true, executes the request immediately on mount. |
157
+ | `debounce` | `number` | `undefined` | Milliseconds to delay execution (only for `autoFetch`). |
158
+
159
+ #### Return Values
160
+ | Property | Type | Description |
161
+ |----------|------|-------------|
162
+ | `data` | `T \| null` | The parsed JSON response. |
163
+ | `error` | `Error \| null` | The error object if the request fails. |
164
+ | `isLoading` | `boolean` | `true` while a request is pending. |
165
+ | `execute` | `(opts) => Promise<T>` | Manually triggers the request with merged options. |
166
+ | `get` | `(url, headers?)` | Helper for GET requests. |
167
+ | `post` | `(url, body, headers?)` | Helper for POST requests. |
168
+ | `put` | `(url, body, headers?)` | Helper for PUT requests. |
169
+ | `patch` | `(url, body, headers?)` | Helper for PATCH requests. |
170
+ | `remove` | `(url, headers?)` | Helper for DELETE requests. |
171
+ | `upload` | `(url, files, name?)` | Helper for file uploads. |
@@ -0,0 +1,18 @@
1
+ import React, { ReactNode } from 'react';
2
+ export interface Interceptors {
3
+ request?: (options: RequestInit) => Promise<RequestInit> | RequestInit;
4
+ response?: (response: Response) => Promise<Response> | Response;
5
+ }
6
+ export interface SmartFetchConfig {
7
+ baseUrl?: string;
8
+ headers?: HeadersInit;
9
+ autoFetch?: boolean;
10
+ onSuccess?: (data: any) => void;
11
+ onError?: (error: Error) => void;
12
+ interceptors?: Interceptors;
13
+ }
14
+ export declare const SmartFetchProvider: ({ children, config }: {
15
+ children: ReactNode;
16
+ config: SmartFetchConfig;
17
+ }) => React.JSX.Element;
18
+ export declare const useSmartFetchConfig: () => SmartFetchConfig;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.useSmartFetchConfig = exports.SmartFetchProvider = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const SmartFetchContext = (0, react_1.createContext)({});
39
+ const SmartFetchProvider = ({ children, config }) => {
40
+ return (react_1.default.createElement(SmartFetchContext.Provider, { value: config }, children));
41
+ };
42
+ exports.SmartFetchProvider = SmartFetchProvider;
43
+ const useSmartFetchConfig = () => (0, react_1.useContext)(SmartFetchContext);
44
+ exports.useSmartFetchConfig = useSmartFetchConfig;
@@ -0,0 +1,26 @@
1
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
2
+ export interface UseSmartFetchOptions<T> {
3
+ url?: string;
4
+ method?: HttpMethod;
5
+ headers?: HeadersInit;
6
+ body?: any;
7
+ autoFetch?: boolean;
8
+ debounce?: number;
9
+ }
10
+ export interface FetchState<T> {
11
+ data: T | null;
12
+ error: Error | null;
13
+ isLoading: boolean;
14
+ }
15
+ export interface UseSmartFetchResult<T> extends FetchState<T> {
16
+ execute: (options?: UseSmartFetchOptions<T>) => Promise<T | null>;
17
+ get: (url: string, headers?: HeadersInit) => Promise<T | null>;
18
+ post: (url: string, body: any, headers?: HeadersInit) => Promise<T | null>;
19
+ put: (url: string, body: any, headers?: HeadersInit) => Promise<T | null>;
20
+ patch: (url: string, body: any, headers?: HeadersInit) => Promise<T | null>;
21
+ remove: (url: string, headers?: HeadersInit) => Promise<T | null>;
22
+ upload: (url: string, fileOrFiles: File | File[] | FormData, fieldName?: string, headers?: HeadersInit, method?: 'POST' | 'PUT') => Promise<T | null>;
23
+ }
24
+ export declare function useSmartFetch<T = any>(initialOptions?: UseSmartFetchOptions<T>): UseSmartFetchResult<T>;
25
+ export { SmartFetchProvider } from './context/SmartFetchContext';
26
+ export default useSmartFetch;
package/dist/index.js ADDED
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SmartFetchProvider = void 0;
13
+ exports.useSmartFetch = useSmartFetch;
14
+ const react_1 = require("react");
15
+ const SmartFetchContext_1 = require("./context/SmartFetchContext");
16
+ function useSmartFetch(initialOptions = {}) {
17
+ const config = (0, SmartFetchContext_1.useSmartFetchConfig)();
18
+ const [state, setState] = (0, react_1.useState)({
19
+ data: null,
20
+ error: null,
21
+ isLoading: !!((initialOptions.autoFetch || config.autoFetch) && (initialOptions.url || config.baseUrl)),
22
+ });
23
+ const optionsRef = (0, react_1.useRef)(initialOptions);
24
+ const abortControllerRef = (0, react_1.useRef)(null);
25
+ (0, react_1.useEffect)(() => {
26
+ optionsRef.current = initialOptions;
27
+ }, [initialOptions]);
28
+ const execute = (0, react_1.useCallback)((...args_1) => __awaiter(this, [...args_1], void 0, function* (options = {}) {
29
+ var _a, _b;
30
+ const mergedOptions = Object.assign(Object.assign({}, optionsRef.current), options);
31
+ let { url, method = 'GET', headers = {}, body } = mergedOptions;
32
+ // Use baseUrl if provided and url is relative
33
+ if (config.baseUrl && url && !url.startsWith('http')) {
34
+ // Ensure no double slashes
35
+ const base = config.baseUrl.endsWith('/') ? config.baseUrl.slice(0, -1) : config.baseUrl;
36
+ const path = url.startsWith('/') ? url : `/${url}`;
37
+ url = `${base}${path}`;
38
+ }
39
+ if (!url) {
40
+ console.error("useSmartFetch: URL is required");
41
+ return null;
42
+ }
43
+ if (abortControllerRef.current) {
44
+ abortControllerRef.current.abort();
45
+ }
46
+ abortControllerRef.current = new AbortController();
47
+ setState(prev => (Object.assign(Object.assign({}, prev), { isLoading: true, error: null })));
48
+ try {
49
+ let fetchOptions = {
50
+ method,
51
+ headers: Object.assign(Object.assign({ 'Content-Type': 'application/json' }, config.headers), headers),
52
+ signal: abortControllerRef.current.signal,
53
+ };
54
+ if (body) {
55
+ if (body instanceof FormData) {
56
+ // If body is FormData, let browser set Content-Type
57
+ // We need to cast headers to any or Record to check/delete
58
+ // But HeadersInit can be Headers object too.
59
+ // Simplest way: don't include Content-Type if it's FormData
60
+ if (fetchOptions.headers && 'Content-Type' in fetchOptions.headers) {
61
+ delete fetchOptions.headers['Content-Type'];
62
+ }
63
+ // Also remove from global headers if merged?
64
+ // Better approach:
65
+ const h = Object.assign(Object.assign({}, config.headers), headers);
66
+ delete h['Content-Type'];
67
+ fetchOptions.headers = h;
68
+ fetchOptions.body = body;
69
+ }
70
+ else if (method !== 'GET') {
71
+ fetchOptions.body = JSON.stringify(body);
72
+ }
73
+ }
74
+ // Request Interceptor
75
+ if ((_a = config.interceptors) === null || _a === void 0 ? void 0 : _a.request) {
76
+ fetchOptions = yield config.interceptors.request(fetchOptions);
77
+ }
78
+ let response = yield fetch(url, fetchOptions);
79
+ // Response Interceptor
80
+ if ((_b = config.interceptors) === null || _b === void 0 ? void 0 : _b.response) {
81
+ response = yield config.interceptors.response(response);
82
+ }
83
+ if (!response.ok) {
84
+ throw new Error(`HTTP error! status: ${response.status}`);
85
+ }
86
+ const data = yield response.json();
87
+ setState({ data, error: null, isLoading: false });
88
+ if (config.onSuccess)
89
+ config.onSuccess(data);
90
+ return data;
91
+ }
92
+ catch (error) {
93
+ if (error.name === 'AbortError') {
94
+ return null;
95
+ }
96
+ setState({ data: null, error, isLoading: false });
97
+ if (config.onError)
98
+ config.onError(error);
99
+ return null;
100
+ }
101
+ }), [config]);
102
+ const stringifiedOptions = JSON.stringify(initialOptions);
103
+ (0, react_1.useEffect)(() => {
104
+ const toggle = initialOptions.autoFetch || config.autoFetch;
105
+ if (toggle) {
106
+ if (initialOptions.url) {
107
+ const timeoutId = setTimeout(() => {
108
+ execute();
109
+ }, initialOptions.debounce || 0);
110
+ return () => {
111
+ clearTimeout(timeoutId);
112
+ if (abortControllerRef.current) {
113
+ abortControllerRef.current.abort();
114
+ }
115
+ };
116
+ }
117
+ }
118
+ return () => {
119
+ if (abortControllerRef.current) {
120
+ abortControllerRef.current.abort();
121
+ }
122
+ };
123
+ }, [stringifiedOptions, execute, config.autoFetch]);
124
+ const get = (0, react_1.useCallback)((url, headers) => execute({ url, method: 'GET', headers }), [execute]);
125
+ const post = (0, react_1.useCallback)((url, body, headers) => execute({ url, method: 'POST', body, headers }), [execute]);
126
+ const put = (0, react_1.useCallback)((url, body, headers) => execute({ url, method: 'PUT', body, headers }), [execute]);
127
+ const patch = (0, react_1.useCallback)((url, body, headers) => execute({ url, method: 'PATCH', body, headers }), [execute]);
128
+ const remove = (0, react_1.useCallback)((url, headers) => execute({ url, method: 'DELETE', headers }), [execute]);
129
+ const upload = (0, react_1.useCallback)((url, fileOrFiles, fieldName = 'file', headers = {}, method = 'POST') => {
130
+ let body;
131
+ if (fileOrFiles instanceof FormData) {
132
+ body = fileOrFiles;
133
+ }
134
+ else {
135
+ body = new FormData();
136
+ if (Array.isArray(fileOrFiles)) {
137
+ fileOrFiles.forEach(file => body.append(fieldName, file));
138
+ }
139
+ else {
140
+ body.append(fieldName, fileOrFiles);
141
+ }
142
+ }
143
+ return execute({ url, method, body, headers });
144
+ }, [execute]);
145
+ return Object.assign(Object.assign({}, state), { execute,
146
+ get,
147
+ post,
148
+ put,
149
+ patch,
150
+ remove,
151
+ upload });
152
+ }
153
+ var SmartFetchContext_2 = require("./context/SmartFetchContext");
154
+ Object.defineProperty(exports, "SmartFetchProvider", { enumerable: true, get: function () { return SmartFetchContext_2.SmartFetchProvider; } });
155
+ exports.default = useSmartFetch;
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "cod-smart-fetch",
3
+ "version": "1.0.0",
4
+ "description": "A smart React hook for fetching data with built-in loading, error, and data states.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "build": "tsc"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/codinnovation/use-smart-fetch.git"
17
+ },
18
+ "keywords": [
19
+ "react",
20
+ "hook",
21
+ "fetch",
22
+ "api",
23
+ "data-fetch"
24
+ ],
25
+ "author": "COD Innovations",
26
+ "license": "ISC",
27
+ "peerDependencies": {
28
+ "react": ">=16.8.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "^18.2.0",
32
+ "react": "^18.2.0",
33
+ "typescript": "^5.9.3"
34
+ },
35
+ "type": "commonjs",
36
+ "bugs": {
37
+ "url": "https://github.com/codinnovation/use-smart-fetch/issues"
38
+ },
39
+ "homepage": "https://github.com/codinnovation/use-smart-fetch#readme"
40
+ }