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