react-smart-crud 0.1.0 → 0.1.1

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
@@ -1,388 +1,430 @@
1
1
 
2
- # react-smart-crud
3
- Smart, minimal, and developer-controlled CRUD engine for React — with **Optimistic UI**, **zero prop-drilling**, and **no state management headache**.
4
2
 
5
- ---
3
+ # react-smart-crud
4
+
5
+
6
+ A **minimal, smart, optimistic CRUD helper for React**
7
+ No Redux. No Zustand. No boilerplate.
6
8
 
7
- ## ðŸ”Ĩ What is react-smart-crud?
9
+ **Designed for api management systems**.
8
10
 
9
- `react-smart-crud` is a **lightweight state + CRUD utility** designed to remove the most painful parts of React CRUD development:
10
11
 
11
- - ❌ No more endless `useState`
12
- - ❌ No more `useEffect` refetch loops
13
- - ❌ No prop drilling between components
14
- - ❌ No forced toast / UI library
15
- - ❌ No Redux / React Query overhead
12
+ ## âœĻ Features
16
13
 
17
- 👉 You write **business logic**, not plumbing.
14
+ - ⚡ Optimistic UI (instant update)
15
+ - 🧠 Global cache (shared across components)
16
+ - â™ŧïļ Auto re-fetch & sync
17
+ - 🔐 Optional auth token support
18
+ - 🔔 Optional toast / notification support
19
+ - ðŸ§Đ Zero external state library
20
+ - ðŸŠķ Very small API surface
18
21
 
19
22
  ---
20
23
 
21
- ## 🧠 Core Idea (Very Important)
24
+ ## ðŸ“Ķ Installation
22
25
 
23
- > **One shared store per resource.
24
- Optimistic first.
25
- Server truth always wins.
26
- Developer controls UI & UX.**
26
+ ```bash
27
+ npm create vite@latest my-project
27
28
 
28
- - Data lives in a **central in-memory store**
29
- - Any component subscribed to that store updates automatically
30
- - CRUD actions update UI instantly (optimistic)
31
- - Server response finalizes or rolls back state
32
- - Errors come directly from backend
29
+ cd my-project
30
+
31
+ npm install react-smart-crud
32
+ ````
33
+
34
+ Optional dependency:
35
+
36
+ ```bash
37
+ npm install react-hot-toast
38
+ ```
33
39
 
34
40
  ---
35
41
 
36
- ## ðŸ˜ĩ Problems This Library Solves
42
+ ## ⚙ïļ One-time Setup (Required)
37
43
 
38
- ### Before (Typical React CRUD)
39
- - `useState` in parent
40
- - `useEffect` for fetch
41
- - Props passed through 3–4 components
42
- - Re-fetch list after every mutation
43
- - Toast logic mixed with API logic
44
- - Server error message lost
44
+ Create a setup file **once** in your app.
45
45
 
46
- ### After (react-smart-crud)
47
- - ✅ No `useState` for list data
48
- - ✅ No `useEffect` refetch
49
- - ✅ No props drilling
50
- - ✅ Instant UI update
51
- - ✅ Manual toast control
52
- - ✅ Real server error shown
46
+ ### 📄 `src/smartCrudConfig.js`
53
47
 
54
- ---
48
+ ```js
49
+ import { setupCrud } from "react-smart-crud";
50
+ import toast from "react-hot-toast";
51
+
52
+ setupCrud({
53
+ baseUrl: "https://jsonplaceholder.typicode.com",
54
+ getToken: () => localStorage.getItem("token"),
55
+ notify: (type, message) => {
56
+ if (type === "success") toast.success(message);
57
+ if (type === "error") toast.error(message);
58
+ },
59
+ });
60
+ ```
55
61
 
56
- ## âœĻ Key Features
62
+ ### 📄 `main.jsx`
57
63
 
58
- ✅ Optimistic Create / Update / Delete
59
- ✅ No `useState` needed for CRUD data
60
- ✅ No `useEffect` dependency hell
61
- ✅ No props drilling between components
62
- ✅ Works across **multiple components automatically**
63
- ✅ Manual toast / notification control
64
- ✅ Backend error message preserved
65
- ✅ Automatic rollback on failure
66
- ✅ REST API friendly
67
- ✅ Extremely small & fast
64
+ ```js
65
+ import "./smartCrudConfig";
66
+ ```
67
+
68
+ ⚠ïļ **Do this only once** in your app.
68
69
 
69
70
  ---
70
71
 
71
- ## ðŸ‘Ĩ Who Is This For?
72
+ ## 🧠 useCrud Hook
73
+
74
+ ```js
75
+ const { data, loading, error } = useCrud("users");
76
+ ```
72
77
 
73
- ### Perfect for:
74
- - React dashboard projects
75
- - Admin panels
76
- - School / ERP / CRM systems
77
- - MERN stack apps
78
- - Freelancers & agencies
79
- - Developers tired of over-engineering
78
+ ### Returned values
80
79
 
81
- ### Not meant for:
82
- - Offline-first apps
83
- - GraphQL heavy caching
84
- - Real-time sync systems
80
+ | key | type | description |
81
+ | ------- | ------- | ------------- |
82
+ | data | array | cached data |
83
+ | loading | boolean | request state |
84
+ | error | any | error info |
85
85
 
86
86
  ---
87
87
 
88
- ## 🆚 Comparison With Existing Solutions
88
+ ## ✍ïļ Create (POST)
89
89
 
90
- | Feature | react-smart-crud | React Query | Redux |
91
- |------|------------------|-------------|-------|
92
- | useState needed | ❌ No | ❌ No | ❌ No |
93
- | useEffect needed | ❌ No | ❌ No | ❌ No |
94
- | Prop drilling | ❌ No | ❌ No | ❌ No |
95
- | Optimistic UI | ✅ Simple | ⚠ïļ Complex | ⚠ïļ Manual |
96
- | Toast control | ✅ Full | ❌ Indirect | ❌ Indirect |
97
- | Boilerplate | ðŸ”Ĩ Very Low | Medium | High |
98
- | Learning curve | ⭐ Easy | ⭐⭐ Medium | ⭐⭐⭐ Hard |
90
+ ```js
91
+ createItem("users", { name: "John" });
92
+ ```
93
+
94
+ ### With optimistic UI
95
+
96
+ ```js
97
+ createItem(
98
+ "users",
99
+ { name: "John" },
100
+ {
101
+ optimistic: (data) => data,
102
+ onSuccess: () => console.log("Created"),
103
+ onError: (err) => console.error(err),
104
+ }
105
+ );
106
+ ```
99
107
 
100
108
  ---
101
109
 
102
- ## ðŸ“Ķ Installation
110
+ ## 🔄 Update (PUT)
103
111
 
104
- ```
105
- npm install react-smart-crud
112
+ ```js
113
+ updateItem("users", 1, { name: "Updated" });
106
114
  ```
107
115
 
116
+ ---
108
117
 
109
- Optional (for UI notifications)
118
+ ## ❌ Delete (DELETE)
110
119
 
111
- ```
112
- npm install react-hot-toast
120
+ ```js
121
+ deleteItem("users", 1);
113
122
  ```
114
123
 
124
+ ---
115
125
 
116
- ⚠ïļ **Toast library is NOT required**
126
+ ## 📂 Example Endpoints
117
127
 
118
- You can use:
128
+ | Action | Endpoint |
129
+ | ------ | ----------------- |
130
+ | Fetch | GET /users |
131
+ | Create | POST /users |
132
+ | Update | PUT /users/:id |
133
+ | Delete | DELETE /users/:id |
119
134
 
120
- * Modal
121
- * Alert
122
- * Snackbar
123
- * Custom UI
124
- * Or nothing at all
135
+ ---
136
+
137
+ ## 🧊 Works With
138
+
139
+ * REST APIs
140
+ * Laravel / Express / Django
141
+ * Admin dashboards
142
+ * School / Business management systems
143
+ * Small to mid projects
125
144
 
126
145
  ---
127
146
 
128
- ## 🗂ïļ Recommended Folder Structure
147
+ ## ðŸ§Đ Philosophy
129
148
 
130
- ```txt
131
- src/
132
- ├─ smart-crud/
133
- │ ├─ config.js # baseUrl & token config
134
- │ ├─ http.js # fetch wrapper
135
- │ ├─ store.js # central data store
136
- │ ├─ crud.js # create / update / delete
137
- │ └─ index.js # exports
138
- ```
149
+ > Simple cache + smart subscribers
150
+ > No unnecessary abstraction
151
+ > Let React re-render naturally
139
152
 
140
153
  ---
141
154
 
142
- ## ⚙ïļ Configuration
155
+ ## 📄 License
143
156
 
144
- ### `config.js`
157
+ MIT ÂĐ Tarequl Islam
145
158
 
146
- ```js
147
- export const config = {
148
- baseUrl: "",
149
- getToken: null,
150
- notify: null // ✅ toast handler
151
- }
152
159
 
153
- export function setupCrud(options = {}) {
154
- config.baseUrl = options.baseUrl || ""
155
- config.getToken = options.getToken || null
156
- config.notify = options.notify || null
157
- }
158
160
 
159
- ```
160
161
 
161
- ### Why this design?
162
162
 
163
- * `baseUrl` → auto applied everywhere
164
- * `getToken` → optional, dynamic
165
- * Works with:
163
+ ## ✅ REAL-WORLD EXAMPLE (Vite + React)
166
164
 
167
- * JWT
168
- * Cookie-based auth
169
- * Public APIs
165
+ ### 📄 `UserPage.jsx`
170
166
 
171
- ---
167
+ ```jsx
168
+ import { useCrud, createItem, deleteItem } from "react-smart-crud";
172
169
 
173
- ## 🌐 HTTP Layer (Server Error Safe)
170
+ export default function UserPage() {
171
+ const { data: users, loading, error } = useCrud("users");
174
172
 
175
- ```js
176
- import { config } from "./config";
177
-
178
- export async function request(url, options = {}) {
179
- const headers = {
180
- "Content-Type": "application/json",
181
- ...(options.headers || {}),
182
- };
183
-
184
- // 🔐 token optional
185
- if (config.getToken) {
186
- const token = config.getToken();
187
- if (token) {
188
- headers.Authorization = `Bearer ${token}`;
189
- }
190
- }
173
+ if (loading) return <p>Loading...</p>;
174
+ if (error) return <p>Something went wrong</p>;
191
175
 
192
- const res = await fetch(config.baseUrl + url, {
193
- ...options,
194
- headers,
195
- });
196
-
197
- // ðŸŸĒ body safe parse
198
- const data = await res.json().catch(() => ({}));
199
-
200
- // ðŸ”ī IMPORTANT FIX
201
- if (!res.ok) {
202
- throw {
203
- status: res.status,
204
- message: data.message || "Something went wrong",
205
- data,
206
- };
207
- }
176
+ return (
177
+ <div style={{ padding: 20 }}>
178
+ <h2>Users</h2>
208
179
 
209
- return data;
180
+ <button
181
+ onClick={() =>
182
+ createItem("users", {
183
+ name: "New User",
184
+ email: "test@mail.com",
185
+ })
186
+ }
187
+ >
188
+ ➕ Add User
189
+ </button>
190
+
191
+ <ul>
192
+ {users.map((u) => (
193
+ <li key={u.id}>
194
+ {u.name}
195
+ <button onClick={() => deleteItem("users", u.id)}>
196
+ ❌
197
+ </button>
198
+ </li>
199
+ ))}
200
+ </ul>
201
+ </div>
202
+ );
210
203
  }
204
+ ````
211
205
 
212
- ```
206
+
207
+ ---
213
208
 
214
- ### Benefits
209
+ ## ðŸ”Ĩ Optimistic UI – Full Explanation (ADD THIS)
215
210
 
216
- ✔ Backend error message preserved
217
- ✔ UI controls error display
218
- ✔ No generic error forcing
211
+ ### ðŸŽŊ Why Optimistic UI?
219
212
 
220
- ---
213
+ Optimistic UI means:
221
214
 
222
- ## 🧠 Store Concept (No useState, No Props)
215
+ > **Server response ā͆āĶļāĶūāͰ ā͆ā͗⧇ā͇ UI update āĶđāĶŽā§‡**
216
+ > Error āĶđāĶē⧇ auto rollback āĶđāĶŽā§‡
223
217
 
224
- * One store per resource
225
- * Shared across all components
226
- * Subscribers auto re-render
218
+ react-smart-crud āĶ āĶā͟āĶū **fully optional**āĨĪ
227
219
 
228
- ### What you DON’T do anymore
220
+ ---
229
221
 
230
- * ❌ No `useState` for lists
231
- * ❌ No `useEffect` for fetching
232
- * ❌ No prop drilling
233
- * ❌ No manual syncing
222
+ ## 🧠 Optimistic Options Structure
223
+
224
+ Every mutation (`createItem`, `updateItem`, `deleteItem`) supports:
225
+
226
+ ```ts
227
+ {
228
+ optimistic?: Function
229
+ onSuccess?: Function
230
+ onError?: Function
231
+ }
232
+ ```
234
233
 
235
234
  ---
236
235
 
237
- ## ✍ïļ Usage Examples
236
+ ## ðŸŸĒ CREATE with Optimistic UI
238
237
 
239
- ### CREATE (Optimistic + Toast)
238
+ ### Example
240
239
 
241
240
  ```js
242
- createItem(
243
- "users",
244
- {
245
- email: form.email,
246
- password: form.password,
247
- },
248
- {
249
- optimistic: (data) => ({
250
- email: data.email,
251
- role: "user",
252
- }),
253
-
254
- onSuccess: () => toast.success("User created"),
255
- onError: () => toast.error("Failed to create"),
256
- }
257
- );
241
+ createItem(
242
+ "users",
243
+ {
244
+ email: form.email,
245
+ password: form.password,
246
+ },
247
+ {
248
+ // ðŸ”Ū optimistic preview data
249
+ optimistic: (data) => ({
250
+ email: data.email,
251
+ role: "user",
252
+ }),
253
+
254
+ onSuccess: () => toast.success("User created"),
255
+ onError: () => toast.error("Failed to create"),
256
+ }
257
+ );
258
258
  ```
259
259
 
260
+ ### How it works
261
+
262
+
263
+ 1. Temporary item added instantly
264
+ 2. `_temp: true` flag attached
265
+ 3. Server response merges into same item
266
+ 4. Error āĶđāĶē⧇ rollback
267
+
268
+
260
269
  ---
270
+
271
+
272
+ ## 🔄 UPDATE with Optimistic UI (Advanced)
261
273
 
262
- ### UPDATE (Optimistic Patch)
274
+ ### Example
263
275
 
264
276
  ```js
265
277
  updateItem(
266
- "users",
267
- editingUser.id,
268
- {
269
- email: form.email,
270
- role: form.role,
271
- },
272
- {
273
- optimistic: (old, patch) => ({
274
- ...old,
275
- email: patch.email,
276
- role: patch.role,
277
- }),
278
- onSuccess: () => {
279
- toast.success("Profile updated");
280
- clearEdit(); // ✅ Clear
281
- },
282
- onError: (err) => toast.error(err.message),
283
- }
284
- );
278
+ "users",
279
+ editingUser.id,
280
+ {
281
+ email: form.email,
282
+ role: form.role,
283
+ },
284
+ {
285
+ optimistic: (old, patch) => ({
286
+ ...old,
287
+ email: patch.email,
288
+ role: patch.role,
289
+ }),
290
+
291
+ onSuccess: () => {
292
+ toast.success("Profile updated");
293
+ clearEdit();
294
+ },
295
+
296
+ onError: (err) => toast.error(err.message),
297
+ }
298
+ );
299
+ ```
300
+
301
+ ### Optimistic function signature
302
+
303
+ ```ts
304
+ optimistic: (oldItem, newData) => updatedItem
285
305
  ```
286
306
 
307
+ ✔ You control exactly how UI changes
308
+ ✔ Useful for forms, partial updates, toggle switches
309
+
287
310
  ---
288
311
 
289
- ### DELETE
312
+ ## ❌ DELETE with Manual Error Handling
290
313
 
291
314
  ```js
292
- deleteItem("users", id, {
315
+ deleteItem("users", user.id, {
316
+ onSuccess: () => toast.success("Deleted"),
293
317
  onError: (err) => toast.error(err.message),
294
318
  });
295
319
  ```
296
320
 
297
321
  ---
298
322
 
299
- ## ⚡ Optimistic UI Flow
323
+ ## 🔔 Toast / Notification Integration
300
324
 
301
- 1. UI updates instantly
302
- 2. API request is sent
303
- 3. Server success → finalize data
304
- 4. Server error → rollback
305
- 5. Subscribers re-render automatically
325
+ You can use:
306
326
 
307
- No refetch
308
- No flicker
309
- No confusion
327
+ * react-hot-toast
310
328
 
311
329
  ---
312
330
 
313
- ## ❗ Error Handling (Real Server Message)
331
+ ## 🔧 One-time Setup for Toast
314
332
 
315
- ### Backend
333
+ ### `src/smartCrudConfig.js`
316
334
 
317
335
  ```js
318
- res.status(400).json({ message: "Invalid role" });
336
+ import { setupCrud } from "react-smart-crud";
337
+ import toast from "react-hot-toast";
338
+
339
+ setupCrud({
340
+ baseUrl: "https://your-api.com",
341
+
342
+ notify: (type, message) => {
343
+ if (type === "success") toast.success(message);
344
+ if (type === "error") toast.error(message);
345
+ },
346
+ });
319
347
  ```
320
348
 
321
- ### Frontend
349
+ ---
350
+
351
+ ## 🧠 Manual vs Automatic Notifications
352
+
353
+ ### Automatic (inside library)
354
+
355
+ ```js
356
+ notify("success", "Deleted");
357
+ ```
358
+
359
+ ### Manual (recommended)
322
360
 
323
361
  ```js
324
- onError: (err) => toast.error(err.message);
362
+ createItem("users", data, {
363
+ onSuccess: () => toast.success("Created"),
364
+ onError: (err) => toast.error(err.message),
365
+ });
325
366
  ```
326
367
 
327
- ✔ User sees exact server message
328
- ✔ You control UX
368
+ ✔ Full control
369
+ ✔ Better UX
370
+ ✔ No magic
329
371
 
330
372
  ---
331
373
 
332
- ## ðŸ§Đ Why No useEffect?
374
+ # ðŸ§Đ Summary Table (ADD THIS)
333
375
 
334
- * Data already exists in store
335
- * Subscribers handle re-render
336
- * No dependency array bugs
337
- * No infinite loops
376
+ | Action | Optimistic | Rollback | Manual Toast |
377
+ | ---------- | ------------------ | -------- | ------------ |
378
+ | createItem | ✅ | ✅ | ✅ |
379
+ | updateItem | ✅ | ✅ | ✅ |
380
+ | deleteItem | ❌ (instant remove) | ✅ | ✅ |
338
381
 
339
382
  ---
340
383
 
341
- ## ðŸ§Đ Why No useState?
384
+ # ðŸ’Ą Best Practices (Pro Tips)
342
385
 
343
- * CRUD data is shared
344
- * Multiple components use same data
345
- * Manual syncing is fragile
386
+ ✔ Always return **full object** from optimistic update
346
387
 
347
- ---
388
+ ✔ Keep optimistic logic **UI-only**
348
389
 
349
- ## ðŸ§Đ Why No Prop Drilling?
390
+ ✔ Never trust optimistic data as server truth
350
391
 
351
- * Store is global per resource
352
- * Components subscribe directly
353
- * Clean and scalable
392
+ ✔ Handle toast in component, not inside library
354
393
 
355
394
  ---
356
395
 
357
- ## 🚀 Best Use Cases
358
396
 
359
- * Admin dashboards
360
- * Management systems
361
- * Internal tools
362
- * CRUD-heavy applications
363
- * Rapid MVPs
364
397
 
365
- ---
366
398
 
367
- ## âĪïļ Philosophy
368
399
 
369
- > Simple tools scale better than complex abstractions.
370
400
 
371
- No magic
372
- No hidden behavior
373
- Just predictable CRUD
374
401
 
375
- ---
376
402
 
377
- ## 📄 License
378
403
 
379
- MIT — free to use, modify, and ship.
404
+
405
+
406
+
407
+
408
+
409
+
410
+
380
411
 
381
412
  ---
382
413
 
383
- ## 🙌 Final Note
414
+ ## ✅ How it works (Mental Model)
415
+
416
+ ```
417
+ Component
418
+ ↓
419
+ useCrud("users")
420
+ ↓
421
+ Global store cache
422
+ ↓
423
+ API request (once)
424
+ ↓
425
+ All subscribers auto update
426
+ ```
384
427
 
385
- If you understand basic React,
386
- you already understand **react-smart-crud**.
428
+ 👉 Multiple components → **same data, no duplicate fetch**
387
429
 
388
- Happy coding 🚀
430
+ ---
package/package.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "name": "react-smart-crud",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Minimal optimistic CRUD helper for React without useState, useEffect, or prop drilling",
5
- "main": "dist/index.js",
6
- "module": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
5
+ "type": "./src/index.d.ts",
6
+ "main": "./src/index.js",
7
+ "module": "./src/index.js",
11
8
  "keywords": [
12
9
  "react",
13
10
  "crud",
@@ -17,15 +14,21 @@
17
14
  "admin-dashboard"
18
15
  ],
19
16
  "author": "Tarequl Islam",
20
- "license": "MIT",
21
- "repository": {
22
- "type": "git",
23
- "url": "https://github.com/tareq-dev/react-smart-crud"
17
+ "exports": {
18
+ ".": "./src/index.js"
24
19
  },
20
+ "files": [
21
+ "src"
22
+ ],
25
23
  "peerDependencies": {
26
24
  "react": ">=16.8"
27
25
  },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/tareq-dev/react-smart-crud.git"
29
+ },
28
30
  "devDependencies": {
29
31
  "vite": "^7.3.0"
30
- }
32
+ },
33
+ "license": "MIT"
31
34
  }
package/src/actions.js ADDED
@@ -0,0 +1,114 @@
1
+ import { getEntry } from "./store";
2
+ import { request } from "./http";
3
+ import { notify } from "./notify";
4
+
5
+ /* ================= CREATE ================= */
6
+
7
+ export function createItem(url, data, options = {}) {
8
+ const entry = getEntry(url);
9
+
10
+ /* ========== OPTIMISTIC ========== */
11
+ const optimisticData = options.optimistic ? options.optimistic(data) : data;
12
+
13
+ const tempItem = {
14
+ id: Date.now(),
15
+ ...optimisticData,
16
+ _temp: true,
17
+ };
18
+
19
+ entry.data = [tempItem, ...entry.data];
20
+ entry.subscribers.forEach((fn) => fn());
21
+
22
+ /* ========== REQUEST ========== */
23
+ request(url, {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify(data),
27
+ })
28
+ .then((serverData) => {
29
+ // ✅ MERGE — replace āĶĻāĶū
30
+ entry.data = entry.data.map((item) =>
31
+ item._temp ? { ...item, ...serverData, _temp: false } : item
32
+ );
33
+ notify("success", "Created", { url, data: serverData });
34
+ options.onSuccess?.(serverData);
35
+ })
36
+ .catch((error) => {
37
+ // ðŸ”ī rollback
38
+ entry.data = entry.data.filter((i) => !i._temp);
39
+ notify("error", error.message || "Create failed", {
40
+ url,
41
+ error,
42
+ });
43
+ options.onError?.(error);
44
+ })
45
+ .finally(() => {
46
+ entry.subscribers.forEach((fn) => fn());
47
+ });
48
+ }
49
+
50
+ /* ================= UPDATE ================= */
51
+ export function updateItem(url, id, data, options = {}) {
52
+ const entry = getEntry(url);
53
+ const backup = [...entry.data];
54
+
55
+ // ðŸŸĒ OPTIMISTIC UPDATE
56
+ entry.data = entry.data.map((item) =>
57
+ item.id === id
58
+ ? {
59
+ ...(options.optimistic
60
+ ? options.optimistic(item, data)
61
+ : { ...item, ...data }),
62
+ _updating: true,
63
+ }
64
+ : item
65
+ );
66
+
67
+ entry.subscribers.forEach((fn) => fn());
68
+
69
+ request(`${url}/${id}`, {
70
+ method: "PUT",
71
+ headers: { "Content-Type": "application/json" },
72
+ body: JSON.stringify(data),
73
+ })
74
+ .then((serverData) => {
75
+ // ðŸ”ĩ Merge server data, overwrite āĶĻāĶū
76
+ entry.data = entry.data.map((item) =>
77
+ item.id === id ? { ...item, ...serverData, _updating: false } : item
78
+ );
79
+ notify("success", "Updated", { url, id, data: serverData });
80
+ options.onSuccess?.(serverData);
81
+ })
82
+ .catch((error) => {
83
+ entry.data = backup;
84
+
85
+ notify("error", error.message || "Update failed", {
86
+ url,
87
+ id,
88
+ error,
89
+ });
90
+
91
+ options.onError?.(error);
92
+ })
93
+ .finally(() => entry.subscribers.forEach((fn) => fn()));
94
+ }
95
+
96
+ /* ================= DELETE ================= */
97
+ export function deleteItem(url, id, options = {}) {
98
+ const entry = getEntry(url);
99
+ const backup = [...entry.data];
100
+
101
+ entry.data = entry.data.filter((i) => i.id !== id);
102
+ entry.subscribers.forEach((fn) => fn());
103
+
104
+ request(`${url}/${id}`, { method: "DELETE" })
105
+ .then(() => {
106
+ options.onSuccess?.();
107
+ notify("success", "Deleted");
108
+ })
109
+ .catch((error) => {
110
+ entry.data = backup;
111
+ entry.subscribers.forEach((fn) => fn());
112
+ options.onError?.(error);
113
+ });
114
+ }
package/src/config.js ADDED
@@ -0,0 +1,11 @@
1
+ export const config = {
2
+ baseUrl: "",
3
+ getToken: null,
4
+ notify: null // ✅ toast handler
5
+ }
6
+
7
+ export function setupCrud(options = {}) {
8
+ config.baseUrl = options.baseUrl || ""
9
+ config.getToken = options.getToken || null
10
+ config.notify = options.notify || null
11
+ }
package/src/http.js ADDED
@@ -0,0 +1,35 @@
1
+ import { config } from "./config";
2
+
3
+ export async function request(url, options = {}) {
4
+ const headers = {
5
+ "Content-Type": "application/json",
6
+ ...(options.headers || {}),
7
+ };
8
+
9
+ // 🔐 token optional
10
+ if (config.getToken) {
11
+ const token = config.getToken();
12
+ if (token) {
13
+ headers.Authorization = `Bearer ${token}`;
14
+ }
15
+ }
16
+
17
+ const res = await fetch(config.baseUrl + url, {
18
+ ...options,
19
+ headers,
20
+ });
21
+
22
+ // ðŸŸĒ body safe parse
23
+ const data = await res.json().catch(() => ({}));
24
+
25
+ // ðŸ”ī IMPORTANT FIX
26
+ if (!res.ok) {
27
+ throw {
28
+ status: res.status,
29
+ message: data.message || "Something went wrong",
30
+ data,
31
+ };
32
+ }
33
+
34
+ return data;
35
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ declare module "react-smart-crud" {
2
+ export function setupCrud(options: {
3
+ baseUrl?: string;
4
+ getToken?: () => string | null;
5
+ notify?: (type: string, message: string, meta?: any) => void;
6
+ }): void;
7
+
8
+ export function useCrud<T = any>(url: string): {
9
+ data: T[];
10
+ loading: boolean;
11
+ error: any;
12
+ };
13
+
14
+ export function createItem(
15
+ url: string,
16
+ data: any,
17
+ options?: any
18
+ ): void;
19
+
20
+ export function updateItem(
21
+ url: string,
22
+ id: number | string,
23
+ data: any,
24
+ options?: any
25
+ ): void;
26
+
27
+ export function deleteItem(
28
+ url: string,
29
+ id: number | string,
30
+ options?: any
31
+ ): void;
32
+ }
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { setupCrud } from "./config";
2
+ export { useCrud } from "./useCrud";
3
+ export { createItem, updateItem, deleteItem } from "./actions";
package/src/notify.js ADDED
@@ -0,0 +1,7 @@
1
+ import { config } from "./config";
2
+
3
+ export function notify(type, message, meta = {}) {
4
+ if (typeof config.notify === "function") {
5
+ config.notify(type, message, meta);
6
+ }
7
+ }
package/src/store.js ADDED
@@ -0,0 +1,14 @@
1
+ export const store = new Map();
2
+
3
+ export function getEntry(key) {
4
+ // console.log("ENTRY KEY:", key);
5
+ if (!store.has(key)) {
6
+ store.set(key, {
7
+ data: [],
8
+ loading: false,
9
+ error: null,
10
+ subscribers: new Set(),
11
+ });
12
+ }
13
+ return store.get(key);
14
+ }
package/src/useCrud.js ADDED
@@ -0,0 +1,39 @@
1
+ import { useEffect, useState } from "react";
2
+ import { getEntry } from "./store";
3
+ import { request } from "./http";
4
+
5
+ export function useCrud(url) {
6
+ const entry = getEntry(url);
7
+ const [, force] = useState(0);
8
+
9
+ useEffect(() => {
10
+ const rerender = () => force((x) => x + 1);
11
+ entry.subscribers.add(rerender);
12
+
13
+ if (!entry.loading && entry.data.length === 0) {
14
+ entry.loading = true;
15
+
16
+ request(url)
17
+ .then((data) => {
18
+ entry.data = data;
19
+ entry.error = null;
20
+ })
21
+ .catch(() => {
22
+ entry.error = "Failed";
23
+ })
24
+ .finally(() => {
25
+ entry.loading = false;
26
+ entry.subscribers.forEach((fn) => fn());
27
+ });
28
+ }
29
+
30
+ return () => entry.subscribers.delete(rerender);
31
+ }, [url]);
32
+
33
+ // 🔑 IMPORTANT: return new reference
34
+ return {
35
+ data: entry.data,
36
+ loading: entry.loading,
37
+ error: entry.error,
38
+ };
39
+ }