@mongez/atom 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/README.md +862 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
# Atoms
|
|
2
|
+
|
|
3
|
+
A powerful state management tool that could be used within any UI framework or Nodejs App.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
The main purpose of the birth of this package is to work with a simple and performant state management tool to handle data among components and outside components.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Simple and easy to use
|
|
12
|
+
- Won't take more than few minutes to **master it**.
|
|
13
|
+
- Can be used outside components.
|
|
14
|
+
- Listen to atom's value change.
|
|
15
|
+
- Listen to atom's object property change.
|
|
16
|
+
- Lightweight in size.
|
|
17
|
+
- Easy Managing Objects, Arrays, and booleans.
|
|
18
|
+
- Open for Extension using Atom Actions.
|
|
19
|
+
|
|
20
|
+
## React Atom
|
|
21
|
+
|
|
22
|
+
For React users, we have a [dedicated package](https://github.com/hassanzohdy/mongez-react-atom) that works perfectly with React, it has some extra features that would fit with React components.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
`yarn add @mongez/atom`
|
|
27
|
+
|
|
28
|
+
Or
|
|
29
|
+
|
|
30
|
+
`npm i @mongez/atom`
|
|
31
|
+
|
|
32
|
+
Or
|
|
33
|
+
|
|
34
|
+
`pnpm add @mongez/atom`
|
|
35
|
+
|
|
36
|
+
## Atoms are unique
|
|
37
|
+
|
|
38
|
+
Atoms are meant to be **unique** therefore the atom `key` can not be used in more than one atom, if other atom is being created with a previously defined atom, an error will be thrown that indicates to use another atom key.
|
|
39
|
+
|
|
40
|
+
## Create A New Atom
|
|
41
|
+
|
|
42
|
+
The main idea here is every single data that might be manipulated will be stored independently in a shape of an `atom`.
|
|
43
|
+
|
|
44
|
+
This will raise the power of single responsibility.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { createAtom, Atom } from "@mongez/atom";
|
|
48
|
+
|
|
49
|
+
export const currencyAtom: createAtom<string> = createAtom({
|
|
50
|
+
key: "currency",
|
|
51
|
+
default: "EUR",
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> Please note that all atoms are immutables, the default data will be kept untouched if it is an object or an array.
|
|
56
|
+
|
|
57
|
+
When creating a new atom, it's recommended to pass the atom's value type as a generic type to the `atom` function, this will help you use the atom's value in a type-safe way.
|
|
58
|
+
|
|
59
|
+
## Using Atoms
|
|
60
|
+
|
|
61
|
+
Now the `currencyAtom` atom has only single value, from this point we can use it in anywhere in our application components or event outside components.
|
|
62
|
+
|
|
63
|
+
> For demo purposes only, we'll use React in this documentation, but you can use the atom in any UI framework or even in Nodejs.
|
|
64
|
+
|
|
65
|
+
`some-component.ts`
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { useEffect, useState } from "react";
|
|
69
|
+
import { currencyAtom } from "~/src/atoms";
|
|
70
|
+
|
|
71
|
+
export default function Header() {
|
|
72
|
+
const [currency, setCurrency] = useState(currencyAtom.value);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
// Watch for currency changes
|
|
76
|
+
return currencyAtom.onChange(setCurrency);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
<h1>Header</h1>
|
|
82
|
+
Currency: {currency}
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The `onChange` method will listen for any changes happen in the atom and return an [Event Subscription](https://github.com/hassanzohdy/mongez-events#event-subscription) that has `unsubscribe` method to remove the listener, this could be used in the cleanup process.
|
|
89
|
+
|
|
90
|
+
Another way to write the previous use effect for more readability.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const eventSubscription = currencyAtom.onChange(setCurrency);
|
|
95
|
+
return () => eventSubscription.unsubscribe();
|
|
96
|
+
}, []);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Types of atom values
|
|
100
|
+
|
|
101
|
+
Any atom must have a `default` value when initializing it, this value can be any type, it can be a string, number, boolean, object, array, however, when the default value is an `object`, the atom gets a **special treatment**.
|
|
102
|
+
|
|
103
|
+
We will see this later in the documentation.
|
|
104
|
+
|
|
105
|
+
## Get atom value
|
|
106
|
+
|
|
107
|
+
Atom's value can be fetched in different ways, depends what are you trying to do.
|
|
108
|
+
|
|
109
|
+
For example, if you're using the atom outside a `React component` or you're using it inside a component but don't want to rerender the component when the atom's value changes, you can use the `atom.value` property.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// anywhere in your app
|
|
113
|
+
import { currencyAtom } from "~/src/atoms";
|
|
114
|
+
|
|
115
|
+
console.log(currencyAtom.value); // get current value
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Update atom's value
|
|
119
|
+
|
|
120
|
+
The basic way to update atom's value is by using `atom.update`, this method receives the new value of the atom and updates it.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// anywhere in your app
|
|
124
|
+
import { currencyAtom } from "~/src/atoms";
|
|
125
|
+
|
|
126
|
+
currencyAtom.update("USD"); // any component using the atom will be rerendered automatically.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
We can also pass a callback to the update function, the callback will receive the old value and the atom instance.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// anywhere in your app
|
|
133
|
+
import { currencyAtom } from "~/src/atoms";
|
|
134
|
+
|
|
135
|
+
currencyAtom.update((oldValue, atom) => {
|
|
136
|
+
// do something with the old value
|
|
137
|
+
return "USD";
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> Please do remember that `atom.update` must receive a new reference of the value, otherwise it will not trigger the change event, for example `atom.update({ ...user })` will trigger the change event.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// /src/atoms/user-atom.ts
|
|
145
|
+
import { createAtom } from "@mongez/atom";
|
|
146
|
+
|
|
147
|
+
export type UserData = {
|
|
148
|
+
name: string;
|
|
149
|
+
email: string;
|
|
150
|
+
age: number;
|
|
151
|
+
id: number;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const userAtom = createAtom<UserData>({
|
|
155
|
+
key: "user",
|
|
156
|
+
default: {
|
|
157
|
+
name: "Hasan",
|
|
158
|
+
age: 30,
|
|
159
|
+
email: "hassanzohdy@gmail.com",
|
|
160
|
+
id: 1,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Now if we want to make an update for the user atom using `atom.update`, it will be something like this:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// anywhere in your app
|
|
169
|
+
|
|
170
|
+
import { userAtom } from "~/src/atoms/user-atom";
|
|
171
|
+
|
|
172
|
+
userAtom.update({
|
|
173
|
+
...userAtom.value,
|
|
174
|
+
name: "Ahmed",
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Or using callback to get the old value:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// anywhere in your app
|
|
182
|
+
|
|
183
|
+
import { userAtom } from "~/src/atoms/user-atom";
|
|
184
|
+
|
|
185
|
+
userAtom.update((oldValue) => {
|
|
186
|
+
return {
|
|
187
|
+
...oldValue,
|
|
188
|
+
name: "Ahmed",
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Merge atom's value
|
|
194
|
+
|
|
195
|
+
If the atom is an object atom, you can use `atom.merge` to merge the new value with the old value.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
// src/atoms/user-atom.ts
|
|
199
|
+
import { createAtom } from "@mongez/atom";
|
|
200
|
+
|
|
201
|
+
export type UserData = {
|
|
202
|
+
name: string;
|
|
203
|
+
email: string;
|
|
204
|
+
age: number;
|
|
205
|
+
id: number;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const userAtom = createAtom<UserData>({
|
|
209
|
+
key: "user",
|
|
210
|
+
default: {
|
|
211
|
+
name: "Hasan",
|
|
212
|
+
age: 30,
|
|
213
|
+
email: "hassanzohdy@gmail.com",
|
|
214
|
+
id: 1,
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Now if we want to make an update for the user atom using `atom.update`, it will be something like this:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// anywhere in your app
|
|
223
|
+
import { userAtom } from "~/src/atoms";
|
|
224
|
+
|
|
225
|
+
userAtom.update({
|
|
226
|
+
...userAtom.value,
|
|
227
|
+
name: "Ahmed",
|
|
228
|
+
age: 25,
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
If you notice, we've to spread the old value and then add the new values, this is good, but we can use `atom.merge` instead.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// anywhere in your app
|
|
236
|
+
import { userAtom } from "~/src/atoms";
|
|
237
|
+
|
|
238
|
+
userAtom.merge({
|
|
239
|
+
name: "Ahmed",
|
|
240
|
+
age: 25,
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
This is just a shortcut for `atom.update`, it will merge the new value with the old value and then update the atom.
|
|
245
|
+
|
|
246
|
+
## On Atom Reset
|
|
247
|
+
|
|
248
|
+
To listen to atom when it is reset, use `onReset` method.
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
// anywhere in your app
|
|
252
|
+
import { currencyAtom } from "~/src/atoms";
|
|
253
|
+
|
|
254
|
+
currencyAtom.onReset((atom) => {
|
|
255
|
+
//
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
> This will be triggered after the update event is triggered
|
|
260
|
+
|
|
261
|
+
## Changing only single key in the atom's value
|
|
262
|
+
|
|
263
|
+
Instead of passing the whole object to the `setUser` function, we can pass only the key we want to change using `atom.change` function.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import React from "react";
|
|
267
|
+
import { userAtom } from "~/src/atoms";
|
|
268
|
+
|
|
269
|
+
export default function UserForm() {
|
|
270
|
+
const [user, setUser] = React.useState(userAtom.value);
|
|
271
|
+
|
|
272
|
+
React.useEffect(() => {
|
|
273
|
+
return userAtom.onChange(setUser);
|
|
274
|
+
}, []);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<>
|
|
278
|
+
<h1>User Form</h1>
|
|
279
|
+
<input
|
|
280
|
+
type="text"
|
|
281
|
+
value={user.name}
|
|
282
|
+
onChange={(e) => userAtom.change("name", e.target.value)}
|
|
283
|
+
/>
|
|
284
|
+
<input
|
|
285
|
+
type="text"
|
|
286
|
+
value={user.email}
|
|
287
|
+
onChange={(e) => userAtom.change("email", e.target.value)}
|
|
288
|
+
/>
|
|
289
|
+
</>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
This will change only the given key in the atom's value, and trigger a component rerender if the atom's value is used in the component.
|
|
295
|
+
|
|
296
|
+
> Please note that `change` method calls `update` method under the hood, so it will generate a new object.
|
|
297
|
+
|
|
298
|
+
## Get Atom single key value
|
|
299
|
+
|
|
300
|
+
If atom's value is an object, we can get a value from the atom directly using `atom.get` function.
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { createAtom } from "@mongez/atom-react";
|
|
304
|
+
|
|
305
|
+
const userAtom = createAtom({
|
|
306
|
+
key: "user",
|
|
307
|
+
default: {
|
|
308
|
+
key: "Hasan",
|
|
309
|
+
address: {
|
|
310
|
+
city: "New York",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
console.log(userAtom.get("key")); // Hasan
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Dot Notation is also supported.
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
console.log(userAtom.get("address.city")); // New York
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
If key doesn't exist, return default value instead.
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
console.log(userAtom.get("email", "default@email.com")); // default@email.com
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Reset value
|
|
331
|
+
|
|
332
|
+
This feature might be useful in some scenarios when we need to reset the atom's value to its default value.
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
// anywhere in your app
|
|
336
|
+
import { currencyAtom } from "~/src/atoms";
|
|
337
|
+
|
|
338
|
+
currencyAtom.reset(); // any component using the atom will be rerendered automatically.
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
This will trigger an atom update and set the atom's value to its default value.
|
|
342
|
+
|
|
343
|
+
## Silent Update (Update without triggering change event)
|
|
344
|
+
|
|
345
|
+
Works exactly like `update` method, but it will not trigger the change event.
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
// anywhere in your app
|
|
349
|
+
import { currencyAtom } from "~/src/atoms";
|
|
350
|
+
|
|
351
|
+
currencyAtom.silentUpdate("USD"); // any component using the atom will be rerendered automatically.
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Silent Reset Value (Reset without triggering change event)
|
|
355
|
+
|
|
356
|
+
Sometimes its useful to reset the atom's value to its default value without triggering the change event, this can be achieved using `silentReset` method, a good sue case for this is when a component is unmounted and you want to reset the atom's value to its default value without triggering the change event.
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
// Header.tsx
|
|
360
|
+
import { currencyAtom } from "~/src/atoms";
|
|
361
|
+
import { useEffect } from "react";
|
|
362
|
+
|
|
363
|
+
export default function Header() {
|
|
364
|
+
const currency = currencyAtom.useValue();
|
|
365
|
+
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
return () => currencyAtom.silentReset();
|
|
368
|
+
}, []);
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<>
|
|
372
|
+
<h1>Header</h1>
|
|
373
|
+
Currency: {currency}
|
|
374
|
+
</>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This will not trigger the value change event, but it will reset the atom's value to its default value and **the reset event will be triggered though**
|
|
380
|
+
|
|
381
|
+
## Silent Change (Change without triggering change event)
|
|
382
|
+
|
|
383
|
+
Works exactly like `change` method, but it will not trigger the change event.
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
// anywhere in your app
|
|
387
|
+
import { userAtom } from "~/src/atoms";
|
|
388
|
+
|
|
389
|
+
userAtom.silentChange("name", "Ahmed");
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Destroy atom
|
|
393
|
+
|
|
394
|
+
We can also destroy the atom using `destroy()` method from the atom, it will automatically fire the `onDestroy` event.
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
// anywhere in your app
|
|
398
|
+
import { currencyAtom } from "~/src/atoms";
|
|
399
|
+
|
|
400
|
+
currencyAtom.destroy();
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Getting atom key
|
|
404
|
+
|
|
405
|
+
To get the atom key, use `atom.key` will return the atom key.
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
// anywhere in your app
|
|
409
|
+
import { currencyAtom } from "~/src/atoms";
|
|
410
|
+
|
|
411
|
+
console.log(currencyAtom.key); // currencyAtom
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Getting all atoms
|
|
415
|
+
|
|
416
|
+
To list all registered atoms, use `atomsList` utility for that purpose.
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
// anywhere in your app
|
|
420
|
+
import { atomsList } from "~/src/atoms";
|
|
421
|
+
|
|
422
|
+
console.log(atomsList()); // [currencyAtom, ...]
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## get handler function
|
|
426
|
+
|
|
427
|
+
Sometimes we may need to handle the `atom.get` function to get the data in a customized way, we can achieve this by defining in the atom function call how the atom will retrieve the object's value.
|
|
428
|
+
|
|
429
|
+
Without Defining the `atom getter`
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const settingsAtom = createAtom({
|
|
433
|
+
key: "user",
|
|
434
|
+
default: {
|
|
435
|
+
isLoaded: false,
|
|
436
|
+
settings: {},
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// later
|
|
441
|
+
settingsAtom.update({
|
|
442
|
+
isLoaded: true,
|
|
443
|
+
settings: {
|
|
444
|
+
websiteName: "My Website Name",
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
console.log(userAtom.get("settings.websiteName")); // My Website Name
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
After Defining it
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
import { createAtom } from "@mongez/atom-react";
|
|
455
|
+
|
|
456
|
+
const settingsAtom = createAtom({
|
|
457
|
+
key: "settings",
|
|
458
|
+
default: {
|
|
459
|
+
isLoaded: false,
|
|
460
|
+
settings: {},
|
|
461
|
+
},
|
|
462
|
+
get(key: string, defaultValue: any = null, atomValue: any) {
|
|
463
|
+
return atomValue[key] !== undefined
|
|
464
|
+
? atomValue[key]
|
|
465
|
+
: atomValue.settings[key] !== undefined
|
|
466
|
+
? atomValue.settings[key]
|
|
467
|
+
: defaultValue;
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// later
|
|
472
|
+
settingsAtom.update({
|
|
473
|
+
isLoaded: true,
|
|
474
|
+
settings: {
|
|
475
|
+
websiteName: "My Website Name",
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
console.log(settingsAtom.get("websiteName")); // My Website Name
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Listen to atom value changes
|
|
483
|
+
|
|
484
|
+
This is what happens with `useAtom` hook, it listens to the atom's value change using `onChange` method.
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
// anywhere in your app
|
|
488
|
+
import { currencyAtom } from "~/src/atoms";
|
|
489
|
+
|
|
490
|
+
currencyAtom.onChange((newValue, oldValue, atom) => {
|
|
491
|
+
//
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
> Please note the `onChange` is returning an [EventSubscription](https://github.com/hassanzohdy/mongez-events#unsubscribe-to-event) instance, we can remove the listener anytime, for example when unmounting the component.
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
// anywhere in your app
|
|
499
|
+
import { currencyAtom } from "~/src/atoms";
|
|
500
|
+
|
|
501
|
+
// in your component...
|
|
502
|
+
const [currency, setCurrency] = useState(currencyAtom.value);
|
|
503
|
+
useEffect(() => {
|
|
504
|
+
const onCurrencyChange = currencyAtom.onChange(setCurrency);
|
|
505
|
+
return () => onCurrencyChange.unsubscribe();
|
|
506
|
+
}, []);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Watch For Partial Change
|
|
510
|
+
|
|
511
|
+
Sometimes you may need to watch for only a key in the atom's value object, the `atom.watch` function is the perfect way to achieve this.
|
|
512
|
+
|
|
513
|
+
> Please note this only works if the atom's default is an object or an array.
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
// anywhere in your app
|
|
517
|
+
import { createAtom } from "@mongez/atom";
|
|
518
|
+
|
|
519
|
+
const userAtom = createAtom({
|
|
520
|
+
key: "user",
|
|
521
|
+
default: {
|
|
522
|
+
key: "Hasan",
|
|
523
|
+
address: {
|
|
524
|
+
city: "New York",
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
userAtom.watch("key", (newName, oldName) => {
|
|
530
|
+
console.log(newName, oldName); // 'Hasan', 'Ali'
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// later in the app
|
|
534
|
+
userAtom.update({
|
|
535
|
+
...userAtom.value,
|
|
536
|
+
key: "Ali",
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
> Dot notation is allowed too.
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
// anywhere in your app
|
|
544
|
+
import { createAtom } from "@mongez/atom";
|
|
545
|
+
|
|
546
|
+
const userAtom = createAtom({
|
|
547
|
+
key: "user",
|
|
548
|
+
default: {
|
|
549
|
+
key: "Hasan",
|
|
550
|
+
address: {
|
|
551
|
+
city: "New York",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
userAtom.watch("address.cty", (newCity, oldCity) => {
|
|
557
|
+
console.log(newName, oldName); // 'New York', 'Cairo'
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// later in the app
|
|
561
|
+
userAtom.update({
|
|
562
|
+
...userAtom.value,
|
|
563
|
+
address: {
|
|
564
|
+
...userAtom.value.address,
|
|
565
|
+
city: "Cairo",
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## Value Mutation Before Update
|
|
571
|
+
|
|
572
|
+
Sometimes it's useful to mutate the value before updating it in the atom, this can be achieved via defining `beforeUpdate` method in the atom declaration.
|
|
573
|
+
|
|
574
|
+
This is very useful especially when dealing with objects/arrays and you want to make some operations before using the final value.
|
|
575
|
+
|
|
576
|
+
`beforeUpdate(newValue: any, oldValue: any, atom: Atom)`
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
import { createAtom, Atom } from "@mongez/atom";
|
|
580
|
+
|
|
581
|
+
export const multipleAtom: Atom = createAtom({
|
|
582
|
+
key: "multiple",
|
|
583
|
+
default: 0,
|
|
584
|
+
beforeUpdate(newNumber: number): number {
|
|
585
|
+
return newNumber * 2;
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
multipleAtom.update(4);
|
|
590
|
+
|
|
591
|
+
console.log(multipleAtom.value); // 8
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
## Listen to atom destruction
|
|
595
|
+
|
|
596
|
+
To detect atom destruction when `destroy()` method, use `onDestroy`.
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
// anywhere in your app
|
|
600
|
+
import { currencyAtom } from "~/src/atoms";
|
|
601
|
+
|
|
602
|
+
const subscription = currencyAtom.onDestroy((atom) => {
|
|
603
|
+
//
|
|
604
|
+
});
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
## Atom Type
|
|
608
|
+
|
|
609
|
+
We can get the type of the atom's value using `atom.type` property.
|
|
610
|
+
|
|
611
|
+
```tsx
|
|
612
|
+
const currencyAtom = createAtom({
|
|
613
|
+
key: "currency",
|
|
614
|
+
default: "USD",
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
console.log(currencyAtom.type); // string
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
If the default value is an array it will be returned as array not object.
|
|
621
|
+
|
|
622
|
+
```tsx
|
|
623
|
+
const todoListAtom = createAtom({
|
|
624
|
+
key: "todo",
|
|
625
|
+
default: [],
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
console.log(todoListAtom.type); // array
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
## Atom Actions
|
|
632
|
+
|
|
633
|
+
Sometimes, atoms need to have some actions that can be used to manipulate the atom's value, this can be achieved by defining `actions` object in the atom declaration.
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
import { createAtom, Atom } from "@mongez/atom";
|
|
637
|
+
|
|
638
|
+
export const userAtom: Atom = createAtom({
|
|
639
|
+
key: "user",
|
|
640
|
+
default: {
|
|
641
|
+
name: "Hasan",
|
|
642
|
+
age: 30,
|
|
643
|
+
email: "",
|
|
644
|
+
},
|
|
645
|
+
actions: {
|
|
646
|
+
changeName(name: string) {
|
|
647
|
+
this.update({
|
|
648
|
+
...this.value,
|
|
649
|
+
name,
|
|
650
|
+
});
|
|
651
|
+
},
|
|
652
|
+
changeEmail(email: string) {
|
|
653
|
+
this.update({
|
|
654
|
+
...this.value,
|
|
655
|
+
email,
|
|
656
|
+
});
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// later in the app
|
|
662
|
+
|
|
663
|
+
userAtom.changeName("Ahmed");
|
|
664
|
+
userAtom.changeEmail("");
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
> All action methods are bound to the atom object itself, so feel free to use `this` to access the atom's object.
|
|
668
|
+
|
|
669
|
+
So basically the idea here is simple, we add the actions to the atom's declaration, then we can use these actions to manipulate the atom's value, this is super useful when we have a complex atom that needs to be manipulated in a specific way or if we want to enrich the atom with some actions.
|
|
670
|
+
|
|
671
|
+
> Please note that actions are meant to extend atom functionality, but keep in mind to keep it simple and clean.
|
|
672
|
+
|
|
673
|
+
### Override existing atom methods
|
|
674
|
+
|
|
675
|
+
Actions can be used also to override existing atom functions like `atom.get()` for example.
|
|
676
|
+
|
|
677
|
+
```ts
|
|
678
|
+
import { createAtom, Atom } from "@mongez/atom";
|
|
679
|
+
|
|
680
|
+
export const userAtom: Atom = createAtom({
|
|
681
|
+
key: "user",
|
|
682
|
+
default: {
|
|
683
|
+
name: "Hasan",
|
|
684
|
+
age: 30,
|
|
685
|
+
email: "",
|
|
686
|
+
},
|
|
687
|
+
actions: {
|
|
688
|
+
get(key: string, defaultValue: any = null) {
|
|
689
|
+
return this.value[key] || defaultValue;
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
// later in the app
|
|
695
|
+
|
|
696
|
+
console.log(userAtom.get("name")); // Hasan
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
> Please note this is NOT Recommended to override existing atom methods, there could edge cases for this purpose.
|
|
700
|
+
|
|
701
|
+
### Defining Actions Definition
|
|
702
|
+
|
|
703
|
+
The `createAtom` receives two generic types, the first one is the atom's value type, the second one is the actions definition type.
|
|
704
|
+
|
|
705
|
+
```ts
|
|
706
|
+
import { createAtom, Atom } from "@mongez/atom";
|
|
707
|
+
|
|
708
|
+
type UserActions = {
|
|
709
|
+
changeName(name: string): void;
|
|
710
|
+
changeEmail(email: string): void;
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
export const userAtom: Atom = createAtom<UserData, UserActions>({
|
|
714
|
+
key: "user",
|
|
715
|
+
default: {
|
|
716
|
+
name: "Hasan",
|
|
717
|
+
age: 30,
|
|
718
|
+
email: "",
|
|
719
|
+
},
|
|
720
|
+
actions: {
|
|
721
|
+
changeName(name: string) {
|
|
722
|
+
this.update({
|
|
723
|
+
...this.value,
|
|
724
|
+
name,
|
|
725
|
+
});
|
|
726
|
+
},
|
|
727
|
+
changeEmail(email: string) {
|
|
728
|
+
this.update({
|
|
729
|
+
...this.value,
|
|
730
|
+
email,
|
|
731
|
+
});
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
});
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
It's recommended to define the actions type to have a better type checking and auto-completion.
|
|
738
|
+
|
|
739
|
+
## Working with atom as arrays
|
|
740
|
+
|
|
741
|
+
To treat the atom as an array, we will need to use the `atomCollection` function instead of `createAtom`, it provides more array functions that will help us to manipulate the atom's value.
|
|
742
|
+
|
|
743
|
+
```ts
|
|
744
|
+
import { atomCollection } from "@mongez/atom";
|
|
745
|
+
|
|
746
|
+
const todoListAtom = atomCollection({
|
|
747
|
+
key: "todo",
|
|
748
|
+
default: [],
|
|
749
|
+
});
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
Now we can use the following functions to manipulate the atom's value.
|
|
753
|
+
|
|
754
|
+
### Add item to the end of the array
|
|
755
|
+
|
|
756
|
+
```ts
|
|
757
|
+
todoListAtom.push("Buy Milk");
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
> It can accept multiple items to add.
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
todoListAtom.push("Buy Milk", "Buy Bread");
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### Add item to the beginning of the array
|
|
767
|
+
|
|
768
|
+
```ts
|
|
769
|
+
todoListAtom.unshift("Buy Milk");
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
> It can accept multiple items to add.
|
|
773
|
+
|
|
774
|
+
```ts
|
|
775
|
+
todoListAtom.unshift("Buy Milk", "Buy Bread");
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Remove item from the end of the array
|
|
779
|
+
|
|
780
|
+
```ts
|
|
781
|
+
todoListAtom.pop();
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### Remove item from the beginning of the array
|
|
785
|
+
|
|
786
|
+
```ts
|
|
787
|
+
todoListAtom.shift();
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Update item in the array
|
|
791
|
+
|
|
792
|
+
```ts
|
|
793
|
+
todoListAtom.replace(0, "Buy Bread");
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
### Get item from the array
|
|
797
|
+
|
|
798
|
+
There are two ways to get an item from the array, either by sending the item index, or passing a callback to find the item with:
|
|
799
|
+
|
|
800
|
+
```ts
|
|
801
|
+
// by index
|
|
802
|
+
console.log(todoListAtom.get(0)); // Buy Milk
|
|
803
|
+
|
|
804
|
+
// by callback
|
|
805
|
+
console.log(todoListAtom.get((item) => item === "Buy Milk")); // Buy Milk
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
If the item is not found, it will return `undefined`.
|
|
809
|
+
|
|
810
|
+
### Remove item from the array
|
|
811
|
+
|
|
812
|
+
We can either pass the item index or a callback to remove the item from the array.
|
|
813
|
+
|
|
814
|
+
```ts
|
|
815
|
+
// by index
|
|
816
|
+
todoListAtom.remove(0);
|
|
817
|
+
|
|
818
|
+
// by callback
|
|
819
|
+
todoListAtom.remove((item) => item === "Buy Milk");
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### Remove item from the array by index
|
|
823
|
+
|
|
824
|
+
To remove the itm from the array using the item itself, just pass it to `removeItem` method:
|
|
825
|
+
|
|
826
|
+
```ts
|
|
827
|
+
todoListAtom.removeItem("Buy Milk");
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
This will remove the first found item in the array.
|
|
831
|
+
|
|
832
|
+
### Remove all found items from the array
|
|
833
|
+
|
|
834
|
+
If element possibly exists more than once in the array, we can remove all of them using `removeAll` method.
|
|
835
|
+
|
|
836
|
+
```ts
|
|
837
|
+
todoListAtom.removeAll("Buy Milk");
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Map over the array
|
|
841
|
+
|
|
842
|
+
The `map` method will update the atom's array with the new array.
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
todoListAtom.map((item) => item.toUpperCase());
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Get the array length
|
|
849
|
+
|
|
850
|
+
```ts
|
|
851
|
+
console.log(todoListAtom.length); // 2
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
### Reset the array
|
|
855
|
+
|
|
856
|
+
Just use the normal `reset` method.
|
|
857
|
+
|
|
858
|
+
## Change Log
|
|
859
|
+
|
|
860
|
+
## V1.0.0 (12 May 2024)
|
|
861
|
+
|
|
862
|
+
- Initial release
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mongez/atom",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An agnostic state management tool that work with any framework on browser or server",
|
|
5
|
+
"main": "./cjs/index.js",
|
|
6
|
+
"author": "hassanzohdy",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/hassanzohdy/atom"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@mongez/events": "^2.1.0",
|
|
14
|
+
"@mongez/reinforcements": "^2.3.10"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react",
|
|
18
|
+
"state",
|
|
19
|
+
"atom",
|
|
20
|
+
"state management",
|
|
21
|
+
"state management tool",
|
|
22
|
+
"state management library",
|
|
23
|
+
"recoil",
|
|
24
|
+
"Jotai",
|
|
25
|
+
"redux",
|
|
26
|
+
"angular",
|
|
27
|
+
"svelte",
|
|
28
|
+
"vue",
|
|
29
|
+
"astro",
|
|
30
|
+
"qwik",
|
|
31
|
+
"state management library for react"
|
|
32
|
+
],
|
|
33
|
+
"module": "./esm/index.js",
|
|
34
|
+
"typings": "./cjs/index.d.ts"
|
|
35
|
+
}
|