@fun-tools/store 1.0.1 → 1.0.2
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 +491 -231
- package/dist/chunk-BEP626V3.js +245 -0
- package/dist/chunk-BEP626V3.js.map +1 -0
- package/dist/core/index.cjs +273 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +2 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +11 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +154 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -42
- package/dist/index.d.ts +51 -42
- package/dist/index.js +6 -177
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +273 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +2 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +11 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +49 -40
package/README.md
CHANGED
|
@@ -1,174 +1,229 @@
|
|
|
1
1
|
# @fun-tools/store
|
|
2
2
|
|
|
3
|
-
> A lightweight
|
|
3
|
+
> A simple and lightweight state management library for React apps
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@fun-tools/store)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
## 📋
|
|
8
|
+
## 📋 What is @fun-tools/store?
|
|
9
9
|
|
|
10
|
-
`@fun-tools/store` is
|
|
10
|
+
`@fun-tools/store` is an easy-to-use state management library for React. Think of it as a smarter way to share data between your components without the complexity of Redux or other heavy tools.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**Perfect for beginners and experienced developers alike!**
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
14
|
+
## ✨ Why Choose @fun-tools/store?
|
|
15
|
+
|
|
16
|
+
- ✅ **Super Easy to Learn** - Get started in minutes, not hours
|
|
17
|
+
- ✅ **Automatic Features** - Get built-in functions for free (no need to write repetitive code)
|
|
18
|
+
- ✅ **TypeScript Friendly** - Get helpful suggestions as you type
|
|
19
|
+
- ✅ **Very Small** - Won't bloat your app size
|
|
20
|
+
- ✅ **Fast Performance** - Components only update when they need to
|
|
21
|
+
- ✅ **Works Everywhere** - React, React Native, and Next.js
|
|
21
22
|
|
|
22
23
|
## 📦 Installation
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
npm install @fun-tools/store
|
|
26
|
-
```
|
|
25
|
+
Choose your favorite package manager:
|
|
27
26
|
|
|
28
27
|
```bash
|
|
29
|
-
|
|
28
|
+
npm install @fun-tools/store
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
pnpm add @fun-tools/store
|
|
34
|
-
```
|
|
31
|
+
## 🚀 Quick Start - Your First Store
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
Let's create a simple counter in 3 easy steps:
|
|
37
34
|
|
|
38
|
-
###
|
|
35
|
+
### Step 1: Create Your Store
|
|
39
36
|
|
|
40
37
|
```tsx
|
|
41
|
-
import { createStore } from "@
|
|
38
|
+
import { createStore } from "@fun-tools/store";
|
|
42
39
|
|
|
43
|
-
//
|
|
44
|
-
const
|
|
40
|
+
// Create a store with initial values
|
|
41
|
+
const counterStore = createStore({
|
|
45
42
|
states: {
|
|
46
|
-
count: 0,
|
|
47
|
-
user: { name: "John", age: 25 },
|
|
48
|
-
items: ["apple", "banana"],
|
|
43
|
+
count: 0, // Our counter starts at 0
|
|
49
44
|
},
|
|
50
45
|
});
|
|
46
|
+
```
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
### Step 2: Use It in a Component
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
53
51
|
function Counter() {
|
|
54
|
-
|
|
55
|
-
const
|
|
52
|
+
// Get the count value
|
|
53
|
+
const count = counterStore.useStore((state) => state.count);
|
|
54
|
+
|
|
55
|
+
// Get the handlers (functions to change the state)
|
|
56
|
+
const handlers = counterStore.useHandlers();
|
|
56
57
|
|
|
57
58
|
return (
|
|
58
59
|
<div>
|
|
59
|
-
<
|
|
60
|
+
<h1>Count: {count}</h1>
|
|
61
|
+
{/* Set count to a specific number */}
|
|
62
|
+
<button onClick={() => handlers.count.set(10)}>Set to 10</button>
|
|
63
|
+
{/* Increment using current value */}
|
|
60
64
|
<button onClick={() => handlers.count.set((prev) => prev + 1)}>
|
|
61
|
-
|
|
65
|
+
Add 1
|
|
62
66
|
</button>
|
|
67
|
+
{/* Reset to initial value (0) */}
|
|
63
68
|
<button onClick={() => handlers.count.reset()}>Reset</button>
|
|
64
69
|
</div>
|
|
65
70
|
);
|
|
66
71
|
}
|
|
67
72
|
```
|
|
68
73
|
|
|
69
|
-
|
|
74
|
+
That's it! You have a working counter. 🎉
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
import { createStoreProvider } from "@ex/store";
|
|
76
|
+
## 📖 Core Concepts
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
### 1. Creating a Store
|
|
79
|
+
|
|
80
|
+
A store is where you keep your app's data. It's like a box that holds all your values.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
const myStore = createStore({
|
|
76
84
|
states: {
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
// Put all your data here
|
|
86
|
+
userName: "John",
|
|
87
|
+
age: 25,
|
|
88
|
+
isLoggedIn: false,
|
|
79
89
|
},
|
|
80
90
|
});
|
|
91
|
+
```
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
function App() {
|
|
84
|
-
return (
|
|
85
|
-
<Provider>
|
|
86
|
-
<ThemeToggle />
|
|
87
|
-
</Provider>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
93
|
+
### 2. Reading Data from the Store
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
Use `useStore` to read data in your components:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
function MyComponent() {
|
|
99
|
+
// Method 1: Get one value
|
|
100
|
+
const userName = myStore.useStore((state) => state.userName);
|
|
101
|
+
|
|
102
|
+
// Method 2: Get multiple values
|
|
103
|
+
const { userName, age } = myStore.useStore((state) => ({
|
|
104
|
+
userName: state.userName,
|
|
105
|
+
age: state.age,
|
|
106
|
+
}));
|
|
95
107
|
|
|
96
108
|
return (
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
>
|
|
100
|
-
Current: {theme}
|
|
101
|
-
</button>
|
|
109
|
+
<div>
|
|
110
|
+
Hello, {userName}! You are {age} years old.
|
|
111
|
+
</div>
|
|
102
112
|
);
|
|
103
113
|
}
|
|
104
114
|
```
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
### 3. Changing Data (Using Handlers)
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
Handlers are functions that change your data. The library creates them automatically!
|
|
109
119
|
|
|
110
|
-
|
|
120
|
+
```tsx
|
|
121
|
+
function MyComponent() {
|
|
122
|
+
const handlers = myStore.useHandlers();
|
|
111
123
|
|
|
112
|
-
|
|
124
|
+
// Change the userName
|
|
125
|
+
handlers.userName.set("Jane");
|
|
113
126
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
// Reset to initial value
|
|
128
|
+
handlers.userName.reset();
|
|
129
|
+
}
|
|
130
|
+
```
|
|
117
131
|
|
|
118
|
-
|
|
132
|
+
## 🎨 Auto-Generated Handlers
|
|
119
133
|
|
|
120
|
-
|
|
121
|
-
- `useHandlers()`: Hook to access all handlers
|
|
134
|
+
The best part? You get **FREE handlers** based on your data type!
|
|
122
135
|
|
|
123
|
-
###
|
|
136
|
+
### For Simple Values (String, Number)
|
|
124
137
|
|
|
125
|
-
|
|
138
|
+
```tsx
|
|
139
|
+
const store = createStore({
|
|
140
|
+
states: {
|
|
141
|
+
name: "John",
|
|
142
|
+
age: 25,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
126
145
|
|
|
127
|
-
|
|
146
|
+
const handlers = store.useHandlers();
|
|
128
147
|
|
|
129
|
-
|
|
148
|
+
// ✅ Set to a new value
|
|
149
|
+
handlers.name.set("Jane");
|
|
150
|
+
handlers.age.set(26);
|
|
130
151
|
|
|
131
|
-
|
|
152
|
+
// ✅ Set using current value
|
|
153
|
+
handlers.age.set((currentAge) => currentAge + 1);
|
|
132
154
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
155
|
+
// ✅ Reset to initial value
|
|
156
|
+
handlers.name.reset(); // Back to "John"
|
|
157
|
+
handlers.age.reset(); // Back to 25
|
|
158
|
+
```
|
|
136
159
|
|
|
137
|
-
|
|
160
|
+
### For Boolean (True/False)
|
|
138
161
|
|
|
139
|
-
|
|
162
|
+
```tsx
|
|
163
|
+
const store = createStore({
|
|
164
|
+
states: {
|
|
165
|
+
isOpen: false,
|
|
166
|
+
isDarkMode: true,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
140
169
|
|
|
141
|
-
|
|
170
|
+
const handlers = store.useHandlers();
|
|
142
171
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
172
|
+
// ✅ Toggle (switch between true/false)
|
|
173
|
+
handlers.isOpen.toggle();
|
|
174
|
+
|
|
175
|
+
// ✅ Set to specific value
|
|
176
|
+
handlers.isDarkMode.set(false);
|
|
147
177
|
|
|
148
|
-
// Reset to initial value
|
|
149
|
-
handlers.
|
|
178
|
+
// ✅ Reset to initial value
|
|
179
|
+
handlers.isOpen.reset();
|
|
150
180
|
```
|
|
151
181
|
|
|
152
|
-
### For Arrays
|
|
182
|
+
### For Arrays (Lists)
|
|
153
183
|
|
|
154
184
|
```tsx
|
|
155
185
|
const store = createStore({
|
|
156
|
-
states: {
|
|
186
|
+
states: {
|
|
187
|
+
fruits: ["apple", "banana"],
|
|
188
|
+
numbers: [1, 2, 3],
|
|
189
|
+
},
|
|
157
190
|
});
|
|
158
191
|
|
|
159
192
|
const handlers = store.useHandlers();
|
|
160
193
|
|
|
161
|
-
// Add
|
|
162
|
-
handlers.
|
|
163
|
-
|
|
194
|
+
// ✅ Add to end
|
|
195
|
+
handlers.fruits.push("orange");
|
|
196
|
+
// Result: ["apple", "banana", "orange"]
|
|
197
|
+
|
|
198
|
+
// ✅ Add to beginning
|
|
199
|
+
handlers.fruits.unShift("mango");
|
|
200
|
+
// Result: ["mango", "apple", "banana", "orange"]
|
|
201
|
+
|
|
202
|
+
// ✅ Remove from end
|
|
203
|
+
handlers.fruits.pop();
|
|
204
|
+
// Result: ["mango", "apple", "banana"]
|
|
205
|
+
|
|
206
|
+
// ✅ Remove from beginning
|
|
207
|
+
handlers.fruits.shift();
|
|
208
|
+
// Result: ["apple", "banana"]
|
|
209
|
+
|
|
210
|
+
// ✅ Update item at specific position
|
|
211
|
+
handlers.fruits.update(0, "grape");
|
|
212
|
+
// Result: ["grape", "banana"]
|
|
213
|
+
|
|
214
|
+
// ✅ Update item using current value
|
|
215
|
+
handlers.numbers.update(1, (current) => current * 2);
|
|
164
216
|
|
|
165
|
-
// Remove
|
|
166
|
-
handlers.
|
|
167
|
-
|
|
217
|
+
// ✅ Remove item at specific position
|
|
218
|
+
handlers.fruits.remove(1);
|
|
219
|
+
// Result: ["grape"]
|
|
168
220
|
|
|
169
|
-
//
|
|
170
|
-
handlers.
|
|
171
|
-
|
|
221
|
+
// ✅ Set entire array
|
|
222
|
+
handlers.fruits.set(["kiwi", "melon"]);
|
|
223
|
+
|
|
224
|
+
// ✅ Reset to initial value
|
|
225
|
+
handlers.fruits.reset();
|
|
226
|
+
// Result: ["apple", "banana"]
|
|
172
227
|
```
|
|
173
228
|
|
|
174
229
|
### For Objects
|
|
@@ -178,210 +233,284 @@ const store = createStore({
|
|
|
178
233
|
states: {
|
|
179
234
|
user: {
|
|
180
235
|
name: "John",
|
|
181
|
-
|
|
236
|
+
email: "john@example.com",
|
|
237
|
+
settings: {
|
|
238
|
+
theme: "light",
|
|
239
|
+
notifications: true,
|
|
240
|
+
},
|
|
182
241
|
},
|
|
183
242
|
},
|
|
184
243
|
});
|
|
185
244
|
|
|
186
245
|
const handlers = store.useHandlers();
|
|
187
246
|
|
|
188
|
-
// Update single
|
|
247
|
+
// ✅ Update single property
|
|
189
248
|
handlers.user.update("name", "Jane");
|
|
190
|
-
handlers.user.update("address.city", "LA");
|
|
191
249
|
|
|
192
|
-
// Update
|
|
250
|
+
// ✅ Update with current value
|
|
251
|
+
handlers.user.update("name", (currentName) => currentName.toUpperCase());
|
|
252
|
+
|
|
253
|
+
// ✅ Update nested property (use dot notation)
|
|
254
|
+
handlers.user.update("settings.theme", "dark");
|
|
255
|
+
|
|
256
|
+
// ✅ Update multiple properties at once
|
|
193
257
|
handlers.user.updateMany({
|
|
194
258
|
name: "Jane",
|
|
195
|
-
|
|
259
|
+
email: "jane@example.com",
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ✅ Update nested properties
|
|
263
|
+
handlers.user.updateMany({
|
|
264
|
+
settings: {
|
|
265
|
+
theme: "dark",
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ✅ Set entire object
|
|
270
|
+
handlers.user.set({
|
|
271
|
+
name: "Bob",
|
|
272
|
+
email: "bob@example.com",
|
|
273
|
+
settings: { theme: "blue", notifications: false },
|
|
196
274
|
});
|
|
275
|
+
|
|
276
|
+
// ✅ Reset to initial value
|
|
277
|
+
handlers.user.reset();
|
|
197
278
|
```
|
|
198
279
|
|
|
199
280
|
## 🔧 Custom Handlers
|
|
200
281
|
|
|
201
|
-
|
|
282
|
+
Sometimes you need custom logic. Create your own handlers!
|
|
283
|
+
|
|
284
|
+
### Sync Handlers (Instant Changes)
|
|
202
285
|
|
|
203
286
|
```tsx
|
|
204
287
|
const store = createStore({
|
|
205
288
|
states: {
|
|
206
289
|
count: 0,
|
|
290
|
+
firstName: "John",
|
|
291
|
+
lastName: "Doe",
|
|
207
292
|
},
|
|
293
|
+
|
|
294
|
+
// Define your custom handlers here
|
|
208
295
|
syncHandlers: {
|
|
209
|
-
|
|
210
|
-
|
|
296
|
+
// Handler with no parameters
|
|
297
|
+
increment: (state) => {
|
|
298
|
+
state.count = state.count + 1;
|
|
211
299
|
},
|
|
212
|
-
|
|
213
|
-
|
|
300
|
+
|
|
301
|
+
// Handler with parameters
|
|
302
|
+
incrementBy: (state, amount: number) => {
|
|
303
|
+
state.count = state.count + amount;
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// Handler that changes multiple values
|
|
307
|
+
setFullName: (state, first: string, last: string) => {
|
|
308
|
+
state.firstName = first;
|
|
309
|
+
state.lastName = last;
|
|
214
310
|
},
|
|
215
311
|
},
|
|
216
312
|
});
|
|
217
313
|
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
handlers.
|
|
221
|
-
|
|
314
|
+
// Use them in components
|
|
315
|
+
function MyComponent() {
|
|
316
|
+
const handlers = store.useHandlers();
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div>
|
|
320
|
+
<button onClick={() => handlers.increment()}>Add 1</button>
|
|
321
|
+
<button onClick={() => handlers.incrementBy(5)}>Add 5</button>
|
|
322
|
+
<button onClick={() => handlers.setFullName("Jane", "Smith")}>
|
|
323
|
+
Change Name
|
|
324
|
+
</button>
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
222
328
|
```
|
|
223
329
|
|
|
224
|
-
### Async Handlers
|
|
330
|
+
### Async Handlers (For API Calls)
|
|
331
|
+
|
|
332
|
+
Perfect for fetching data from servers!
|
|
225
333
|
|
|
226
334
|
```tsx
|
|
227
335
|
const store = createStore({
|
|
228
336
|
states: {
|
|
229
337
|
user: null,
|
|
230
338
|
loading: false,
|
|
339
|
+
error: null,
|
|
231
340
|
},
|
|
341
|
+
|
|
232
342
|
asyncHandlers: {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
343
|
+
// Fetch user from API
|
|
344
|
+
fetchUser: async (state, userId: string) => {
|
|
345
|
+
// Set loading to true
|
|
346
|
+
state.loading = true;
|
|
347
|
+
state.error = null;
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
// Fetch from API
|
|
351
|
+
const response = await fetch(`https://api.example.com/users/${userId}`);
|
|
352
|
+
const data = await response.json();
|
|
353
|
+
|
|
354
|
+
// Update state with data
|
|
355
|
+
state.user = data;
|
|
356
|
+
} catch (err) {
|
|
357
|
+
// Handle errors
|
|
358
|
+
state.error = "Failed to fetch user";
|
|
359
|
+
} finally {
|
|
360
|
+
// Set loading to false
|
|
361
|
+
state.loading = false;
|
|
362
|
+
}
|
|
238
363
|
},
|
|
239
364
|
},
|
|
240
365
|
});
|
|
241
366
|
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
## 💡 Advanced Usage
|
|
248
|
-
|
|
249
|
-
### Selective Subscriptions
|
|
250
|
-
|
|
251
|
-
The `useStore` selector ensures components only re-render when selected state changes:
|
|
252
|
-
|
|
253
|
-
```tsx
|
|
254
|
-
// Component only re-renders when 'count' changes
|
|
255
|
-
function CountDisplay() {
|
|
256
|
-
const { count } = store.useStore((state) => ({ count: state.count }));
|
|
257
|
-
return <div>{count}</div>;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Component only re-renders when 'user.name' changes
|
|
261
|
-
function UserName() {
|
|
262
|
-
const { name } = store.useStore((state) => ({
|
|
263
|
-
name: state.user.name,
|
|
367
|
+
// Use in component
|
|
368
|
+
function UserProfile() {
|
|
369
|
+
const { user, loading } = store.useStore((state) => ({
|
|
370
|
+
user: state.user,
|
|
371
|
+
loading: state.loading,
|
|
264
372
|
}));
|
|
265
|
-
|
|
373
|
+
const handlers = store.useHandlers();
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<div>
|
|
377
|
+
<button onClick={() => handlers.fetchUser("123")}>Load User</button>
|
|
378
|
+
{loading && <p>Loading...</p>}
|
|
379
|
+
{user && <p>Name: {user.name}</p>}
|
|
380
|
+
</div>
|
|
381
|
+
);
|
|
266
382
|
}
|
|
267
383
|
```
|
|
268
384
|
|
|
269
|
-
|
|
385
|
+
## 🎁 Using Providers (Scoped Stores)
|
|
270
386
|
|
|
271
|
-
|
|
387
|
+
Sometimes you want a store that only works within a specific part of your app. Use `createStoreProvider`!
|
|
272
388
|
|
|
273
389
|
```tsx
|
|
274
|
-
|
|
275
|
-
states: { user: null, isAuthenticated: false },
|
|
276
|
-
});
|
|
390
|
+
import { createStoreProvider } from "@fun-tools/store";
|
|
277
391
|
|
|
278
|
-
|
|
279
|
-
|
|
392
|
+
// Create a provider
|
|
393
|
+
const { Provider, useStore, useHandlers } = createStoreProvider({
|
|
394
|
+
states: {
|
|
395
|
+
theme: "light",
|
|
396
|
+
language: "en",
|
|
397
|
+
},
|
|
280
398
|
});
|
|
281
399
|
|
|
400
|
+
// Wrap part of your app
|
|
282
401
|
function App() {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
402
|
+
return (
|
|
403
|
+
<Provider>
|
|
404
|
+
<Header />
|
|
405
|
+
<Content />
|
|
406
|
+
</Provider>
|
|
407
|
+
);
|
|
287
408
|
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
### TypeScript Type Inference
|
|
291
|
-
|
|
292
|
-
The library provides full type safety with intelligent inference:
|
|
293
409
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
user: { name: "John", age: 25 },
|
|
299
|
-
},
|
|
300
|
-
syncHandlers: {
|
|
301
|
-
setUserAge: (states, age: number) => {
|
|
302
|
-
states.user.age = age;
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
});
|
|
410
|
+
// Use in any child component
|
|
411
|
+
function Header() {
|
|
412
|
+
const theme = useStore((state) => state.theme);
|
|
413
|
+
const handlers = useHandlers();
|
|
306
414
|
|
|
307
|
-
|
|
415
|
+
return (
|
|
416
|
+
<button
|
|
417
|
+
onClick={() => handlers.theme.set(theme === "light" ? "dark" : "light")}
|
|
418
|
+
>
|
|
419
|
+
Current theme: {theme}
|
|
420
|
+
</button>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
```
|
|
308
424
|
|
|
309
|
-
|
|
310
|
-
handlers.count.set(10); // ✓
|
|
311
|
-
handlers.user.update("name", "Jane"); // ✓
|
|
312
|
-
handlers.setUserAge(30); // ✓
|
|
425
|
+
**The difference:**
|
|
313
426
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
handlers.setUserAge("invalid"); // Error: Argument of type 'string' is not assignable
|
|
317
|
-
```
|
|
427
|
+
- `createStore` = Global (available everywhere)
|
|
428
|
+
- `createStoreProvider` = Scoped (only available inside `<Provider>`)
|
|
318
429
|
|
|
319
|
-
##
|
|
430
|
+
## 💡 Performance Tips
|
|
320
431
|
|
|
321
|
-
|
|
432
|
+
### Only Re-render When Needed
|
|
322
433
|
|
|
323
|
-
|
|
324
|
-
- **WeakMap Storage**: Efficient snapshot management with automatic garbage collection
|
|
325
|
-
- **Immutable Updates**: State changes create new references for proper React updates
|
|
326
|
-
- **Subscription Management**: Automatic cleanup when components unmount
|
|
434
|
+
Components only re-render when the data they use changes:
|
|
327
435
|
|
|
328
|
-
|
|
436
|
+
```tsx
|
|
437
|
+
// ❌ BAD: Component re-renders on ANY state change
|
|
438
|
+
const allState = store.useStore((state) => state);
|
|
329
439
|
|
|
330
|
-
|
|
440
|
+
// ✅ GOOD: Component only re-renders when count changes
|
|
441
|
+
const count = store.useStore((state) => state.count);
|
|
331
442
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
443
|
+
// ✅ GOOD: Component only re-renders when name or age change
|
|
444
|
+
const { name, age } = store.useStore((state) => ({
|
|
445
|
+
name: state.name,
|
|
446
|
+
age: state.age,
|
|
447
|
+
}));
|
|
448
|
+
```
|
|
338
449
|
|
|
339
|
-
##
|
|
450
|
+
## 📚 Real-World Examples
|
|
340
451
|
|
|
341
|
-
### Todo App
|
|
452
|
+
### Example 1: Todo App
|
|
342
453
|
|
|
343
454
|
```tsx
|
|
344
455
|
const todoStore = createStore({
|
|
345
456
|
states: {
|
|
346
|
-
todos: [] as Array<{ id:
|
|
457
|
+
todos: [] as Array<{ id: number; text: string; done: boolean }>,
|
|
347
458
|
},
|
|
459
|
+
|
|
348
460
|
syncHandlers: {
|
|
349
|
-
addTodo: (
|
|
350
|
-
|
|
351
|
-
id: Date.now()
|
|
352
|
-
text,
|
|
353
|
-
|
|
461
|
+
addTodo: (state, text: string) => {
|
|
462
|
+
state.todos.push({
|
|
463
|
+
id: Date.now(),
|
|
464
|
+
text: text,
|
|
465
|
+
done: false,
|
|
354
466
|
});
|
|
355
467
|
},
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
468
|
+
|
|
469
|
+
toggleTodo: (state, id: number) => {
|
|
470
|
+
const todo = state.todos.find((t) => t.id === id);
|
|
471
|
+
if (todo) {
|
|
472
|
+
todo.done = !todo.done;
|
|
473
|
+
}
|
|
359
474
|
},
|
|
360
|
-
|
|
361
|
-
|
|
475
|
+
|
|
476
|
+
deleteTodo: (state, id: number) => {
|
|
477
|
+
state.todos = state.todos.filter((t) => t.id !== id);
|
|
362
478
|
},
|
|
363
479
|
},
|
|
364
480
|
});
|
|
365
481
|
|
|
366
|
-
function
|
|
367
|
-
const
|
|
482
|
+
function TodoApp() {
|
|
483
|
+
const todos = todoStore.useStore((state) => state.todos);
|
|
368
484
|
const handlers = todoStore.useHandlers();
|
|
485
|
+
const [input, setInput] = React.useState("");
|
|
369
486
|
|
|
370
487
|
return (
|
|
371
488
|
<div>
|
|
489
|
+
<input
|
|
490
|
+
value={input}
|
|
491
|
+
onChange={(e) => setInput(e.target.value)}
|
|
492
|
+
placeholder="Add a todo..."
|
|
493
|
+
/>
|
|
494
|
+
<button
|
|
495
|
+
onClick={() => {
|
|
496
|
+
handlers.addTodo(input);
|
|
497
|
+
setInput("");
|
|
498
|
+
}}
|
|
499
|
+
>
|
|
500
|
+
Add
|
|
501
|
+
</button>
|
|
502
|
+
|
|
372
503
|
{todos.map((todo) => (
|
|
373
504
|
<div key={todo.id}>
|
|
374
505
|
<input
|
|
375
506
|
type="checkbox"
|
|
376
|
-
checked={todo.
|
|
507
|
+
checked={todo.done}
|
|
377
508
|
onChange={() => handlers.toggleTodo(todo.id)}
|
|
378
509
|
/>
|
|
379
|
-
<span
|
|
380
|
-
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
|
|
381
|
-
>
|
|
510
|
+
<span style={{ textDecoration: todo.done ? "line-through" : "none" }}>
|
|
382
511
|
{todo.text}
|
|
383
512
|
</span>
|
|
384
|
-
<button onClick={() => handlers.
|
|
513
|
+
<button onClick={() => handlers.deleteTodo(todo.id)}>Delete</button>
|
|
385
514
|
</div>
|
|
386
515
|
))}
|
|
387
516
|
</div>
|
|
@@ -389,49 +518,180 @@ function TodoList() {
|
|
|
389
518
|
}
|
|
390
519
|
```
|
|
391
520
|
|
|
392
|
-
###
|
|
521
|
+
### Example 2: Shopping Cart
|
|
393
522
|
|
|
394
523
|
```tsx
|
|
395
|
-
const
|
|
524
|
+
const cartStore = createStore({
|
|
396
525
|
states: {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
526
|
+
items: [] as Array<{
|
|
527
|
+
id: number;
|
|
528
|
+
name: string;
|
|
529
|
+
price: number;
|
|
530
|
+
quantity: number;
|
|
531
|
+
}>,
|
|
532
|
+
total: 0,
|
|
403
533
|
},
|
|
534
|
+
|
|
404
535
|
syncHandlers: {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
536
|
+
addItem: (state, product: { id: number; name: string; price: number }) => {
|
|
537
|
+
// Check if item already exists
|
|
538
|
+
const existing = state.items.find((item) => item.id === product.id);
|
|
539
|
+
|
|
540
|
+
if (existing) {
|
|
541
|
+
// Increase quantity
|
|
542
|
+
existing.quantity++;
|
|
543
|
+
} else {
|
|
544
|
+
// Add new item
|
|
545
|
+
state.items.push({ ...product, quantity: 1 });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Update total
|
|
549
|
+
state.total = state.items.reduce(
|
|
550
|
+
(sum, item) => sum + item.price * item.quantity,
|
|
551
|
+
0
|
|
552
|
+
);
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
removeItem: (state, id: number) => {
|
|
556
|
+
state.items = state.items.filter((item) => item.id !== id);
|
|
557
|
+
state.total = state.items.reduce(
|
|
558
|
+
(sum, item) => sum + item.price * item.quantity,
|
|
559
|
+
0
|
|
560
|
+
);
|
|
561
|
+
},
|
|
562
|
+
|
|
563
|
+
clearCart: (state) => {
|
|
564
|
+
state.items = [];
|
|
565
|
+
state.total = 0;
|
|
410
566
|
},
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Example 3: User Authentication
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
const authStore = createStore({
|
|
575
|
+
states: {
|
|
576
|
+
user: null as { id: string; name: string; email: string } | null,
|
|
577
|
+
isAuthenticated: false,
|
|
578
|
+
isLoading: false,
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
asyncHandlers: {
|
|
582
|
+
login: async (state, email: string, password: string) => {
|
|
583
|
+
state.isLoading = true;
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
const response = await fetch("/api/login", {
|
|
587
|
+
method: "POST",
|
|
588
|
+
headers: { "Content-Type": "application/json" },
|
|
589
|
+
body: JSON.stringify({ email, password }),
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const data = await response.json();
|
|
593
|
+
|
|
594
|
+
state.user = data.user;
|
|
595
|
+
state.isAuthenticated = true;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
console.error("Login failed:", error);
|
|
598
|
+
} finally {
|
|
599
|
+
state.isLoading = false;
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
|
|
603
|
+
logout: async (state) => {
|
|
604
|
+
await fetch("/api/logout", { method: "POST" });
|
|
605
|
+
state.user = null;
|
|
606
|
+
state.isAuthenticated = false;
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
## 🎓 TypeScript Support
|
|
613
|
+
|
|
614
|
+
The library works great with TypeScript! You get autocomplete and type safety.
|
|
615
|
+
|
|
616
|
+
### Defining State Types
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
// Define your state shape
|
|
620
|
+
type UserState = {
|
|
621
|
+
name: string;
|
|
622
|
+
age: number;
|
|
623
|
+
email: string;
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const store = createStore({
|
|
627
|
+
states: {
|
|
628
|
+
count: 0,
|
|
629
|
+
user: {
|
|
630
|
+
name: "John",
|
|
631
|
+
age: 25,
|
|
632
|
+
email: "john@example.com",
|
|
633
|
+
} as UserState,
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
syncHandlers: {
|
|
637
|
+
// TypeScript knows the state type!
|
|
638
|
+
updateUser: (state, newUser: UserState) => {
|
|
639
|
+
state.user = newUser;
|
|
416
640
|
},
|
|
417
641
|
},
|
|
418
642
|
});
|
|
643
|
+
|
|
644
|
+
// TypeScript will catch errors
|
|
645
|
+
const handlers = store.useHandlers();
|
|
646
|
+
handlers.updateUser({
|
|
647
|
+
name: "Jane",
|
|
648
|
+
age: 26,
|
|
649
|
+
// ❌ Error: missing 'email' property
|
|
650
|
+
});
|
|
419
651
|
```
|
|
420
652
|
|
|
421
|
-
##
|
|
653
|
+
## ❓ Common Questions
|
|
422
654
|
|
|
423
|
-
|
|
655
|
+
### Q: When should I use a global store vs provider?
|
|
424
656
|
|
|
425
|
-
|
|
657
|
+
**Use Global Store (`createStore`) when:**
|
|
658
|
+
|
|
659
|
+
- Data is needed across your entire app (like user auth, theme)
|
|
660
|
+
- You want simple setup without wrapping components
|
|
661
|
+
|
|
662
|
+
**Use Provider (`createStoreProvider`) when:**
|
|
663
|
+
|
|
664
|
+
- Data is only needed in a specific section
|
|
665
|
+
- You want better component isolation
|
|
666
|
+
- You're building reusable components
|
|
667
|
+
|
|
668
|
+
### Q: How is this different from useState?
|
|
669
|
+
|
|
670
|
+
`useState` is great for local component state. Use `@fun-tools/store` when:
|
|
671
|
+
|
|
672
|
+
- Multiple components need the same data
|
|
673
|
+
- You want to avoid prop drilling
|
|
674
|
+
- You need more powerful update functions
|
|
675
|
+
|
|
676
|
+
### Q: Can I use multiple stores?
|
|
677
|
+
|
|
678
|
+
Yes! Create as many stores as you need:
|
|
679
|
+
|
|
680
|
+
```tsx
|
|
681
|
+
const userStore = createStore({ states: { user: null } });
|
|
682
|
+
const cartStore = createStore({ states: { items: [] } });
|
|
683
|
+
const themeStore = createStore({ states: { theme: "light" } });
|
|
684
|
+
```
|
|
426
685
|
|
|
427
|
-
MIT © [Your Name]
|
|
428
686
|
|
|
429
687
|
## 🔗 Links
|
|
430
688
|
|
|
431
689
|
- [GitHub Repository](https://github.com/yourusername/ex-store)
|
|
432
|
-
- [Issues](https://github.com/yourusername/ex-store/issues)
|
|
690
|
+
- [Report Issues](https://github.com/yourusername/ex-store/issues)
|
|
433
691
|
- [NPM Package](https://www.npmjs.com/package/@fun-tools/store)
|
|
434
692
|
|
|
435
693
|
---
|
|
436
694
|
|
|
437
|
-
Made with ❤️
|
|
695
|
+
**Made with ❤️ for developers who value simplicity by @Mustak24**
|
|
696
|
+
|
|
697
|
+
Happy coding! 🚀
|