r-state-tree 0.3.1 → 0.4.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 +244 -19
- package/dist/r-state-tree.cjs +779 -662
- package/dist/r-state-tree.js +772 -672
- package/package.json +2 -1
- package/dist/api.d.ts +0 -9
- package/dist/computedProxy.d.ts +0 -2
- package/dist/decorators.d.ts +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/model/ChildModelsAdministration.d.ts +0 -35
- package/dist/model/Model.d.ts +0 -13
- package/dist/model/ModelAdministration.d.ts +0 -37
- package/dist/model/idMap.d.ts +0 -8
- package/dist/observables/array.d.ts +0 -21
- package/dist/observables/collection.d.ts +0 -43
- package/dist/observables/date.d.ts +0 -4
- package/dist/observables/index.d.ts +0 -6
- package/dist/observables/internal/Administration.d.ts +0 -17
- package/dist/observables/internal/NodeMap.d.ts +0 -23
- package/dist/observables/internal/lookup.d.ts +0 -14
- package/dist/observables/internal/utils.d.ts +0 -9
- package/dist/observables/object.d.ts +0 -25
- package/dist/observables/preact.d.ts +0 -68
- package/dist/store/Store.d.ts +0 -16
- package/dist/store/StoreAdministration.d.ts +0 -31
- package/dist/types.d.ts +0 -77
- package/dist/utils.d.ts +0 -5
package/README.md
CHANGED
|
@@ -8,13 +8,18 @@ Reactive state management featuring store trees, computed child stores, and snap
|
|
|
8
8
|
pnpm add r-state-tree
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
### Requirements
|
|
12
|
+
|
|
13
|
+
This library uses [TC39 Stage 3 Decorators](https://github.com/tc39/proposal-decorators) and requires TypeScript 5.0+ with `target: "es2022"` or higher.
|
|
14
|
+
|
|
15
|
+
The library includes a decorator metadata polyfill for runtimes that don't yet natively support `Symbol.metadata`.
|
|
16
|
+
|
|
11
17
|
## Stores
|
|
12
18
|
|
|
13
19
|
Stores describe reactive state containers composed into a tree.
|
|
14
20
|
|
|
15
21
|
```ts
|
|
16
|
-
import Store,
|
|
17
|
-
import { child } from "r-state-tree/decorators";
|
|
22
|
+
import { Store, createStore, mount, child } from "r-state-tree";
|
|
18
23
|
|
|
19
24
|
class TodoStore extends Store {
|
|
20
25
|
props = { title: "" };
|
|
@@ -44,58 +49,278 @@ import { updateStore } from "r-state-tree";
|
|
|
44
49
|
updateStore(app.todo, { title: "Ship release" });
|
|
45
50
|
```
|
|
46
51
|
|
|
52
|
+
### Child stores
|
|
53
|
+
|
|
54
|
+
Use `@child` for single child stores and `@children` for arrays.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { child, children } from "r-state-tree";
|
|
58
|
+
|
|
59
|
+
class ListStore extends Store {
|
|
60
|
+
items = ["Buy milk", "Walk dog"];
|
|
61
|
+
|
|
62
|
+
@children get todos() {
|
|
63
|
+
return this.items.map((title, i) =>
|
|
64
|
+
createStore(TodoStore, { title, key: i })
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Lifecycle hooks
|
|
71
|
+
|
|
72
|
+
Stores support lifecycle methods:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
class TodoStore extends Store {
|
|
76
|
+
storeDidMount() {
|
|
77
|
+
console.log("Store mounted");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
storeWillUnmount() {
|
|
81
|
+
console.log("Store will unmount");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Reactions
|
|
87
|
+
|
|
88
|
+
Create side effects that run when reactive values change:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
class TodoStore extends Store {
|
|
92
|
+
storeDidMount() {
|
|
93
|
+
this.reaction(
|
|
94
|
+
() => this.props.title,
|
|
95
|
+
(title) => console.log("Title changed:", title)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Context
|
|
102
|
+
|
|
103
|
+
Share data across the store tree without prop drilling:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { createContext } from "r-state-tree";
|
|
107
|
+
|
|
108
|
+
const ThemeContext = createContext<"light" | "dark">("light");
|
|
109
|
+
|
|
110
|
+
class AppStore extends Store {
|
|
111
|
+
theme = "dark";
|
|
112
|
+
|
|
113
|
+
[ThemeContext.provide]() {
|
|
114
|
+
return this.theme;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@child get todo() {
|
|
118
|
+
return createStore(TodoStore);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class TodoStore extends Store {
|
|
123
|
+
get theme() {
|
|
124
|
+
return ThemeContext.consume(this);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const app = mount(createStore(AppStore));
|
|
129
|
+
app.todo.theme; // "dark"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Context is reactive and updates automatically when the provided value changes.
|
|
133
|
+
|
|
47
134
|
### Actions and batching
|
|
48
135
|
|
|
49
|
-
`
|
|
136
|
+
`batch` groups updates to avoid redundant reactions.
|
|
50
137
|
|
|
51
138
|
```ts
|
|
52
|
-
import {
|
|
139
|
+
import { batch } from "r-state-tree";
|
|
53
140
|
|
|
54
|
-
|
|
141
|
+
batch(() => {
|
|
55
142
|
app.todo.props.title = "Refactor";
|
|
56
143
|
});
|
|
57
144
|
```
|
|
58
145
|
|
|
146
|
+
## Observable classes
|
|
147
|
+
|
|
148
|
+
For reactive class instances outside the Model/Store system, use `@observable` and `@computed` decorators:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { Observable, observable, computed, effect } from "r-state-tree";
|
|
152
|
+
|
|
153
|
+
class Counter extends Observable {
|
|
154
|
+
@observable count = 0;
|
|
155
|
+
@observable step = 1;
|
|
156
|
+
|
|
157
|
+
@computed get doubled() {
|
|
158
|
+
return this.count * 2;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
increment() {
|
|
162
|
+
this.count += this.step;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const counter = new Counter();
|
|
167
|
+
|
|
168
|
+
effect(() => {
|
|
169
|
+
console.log("Count:", counter.count, "Doubled:", counter.doubled);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
counter.increment(); // Triggers the effect
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Important:** Class instances require explicit `@observable` and `@computed` decorators. Properties without decorators are **not** reactive.
|
|
176
|
+
|
|
177
|
+
### Plain objects (implicit reactivity)
|
|
178
|
+
|
|
179
|
+
Plain objects wrapped with `observable()` use implicit reactivity for backward compatibility:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { observable, effect } from "r-state-tree";
|
|
183
|
+
|
|
184
|
+
const state = observable({ count: 0 });
|
|
185
|
+
|
|
186
|
+
effect(() => {
|
|
187
|
+
console.log(state.count); // All properties are reactive
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
state.count++; // Triggers the effect
|
|
191
|
+
```
|
|
192
|
+
|
|
59
193
|
## Models and snapshots
|
|
60
194
|
|
|
61
195
|
Models capture persistent state with snapshot utilities.
|
|
62
196
|
|
|
63
197
|
```ts
|
|
64
|
-
import Model from "r-state-tree/model/Model";
|
|
65
198
|
import {
|
|
199
|
+
Model,
|
|
200
|
+
state,
|
|
201
|
+
identifier,
|
|
66
202
|
applySnapshot,
|
|
67
203
|
onSnapshot,
|
|
68
|
-
onSnapshotDiff,
|
|
69
204
|
toSnapshot,
|
|
70
205
|
} from "r-state-tree";
|
|
71
206
|
|
|
72
207
|
class TodoModel extends Model {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
title = "";
|
|
208
|
+
@identifier id = 0;
|
|
209
|
+
@state title = "";
|
|
210
|
+
@state completed = false;
|
|
79
211
|
}
|
|
80
212
|
|
|
81
|
-
const todo =
|
|
213
|
+
const todo = TodoModel.create({ id: 1, title: "Learn signals" });
|
|
82
214
|
|
|
83
215
|
const stop = onSnapshot(todo, (snapshot) => {
|
|
84
|
-
console.log(snapshot
|
|
216
|
+
console.log(snapshot); // { id: 1, title: "Learn signals", completed: false }
|
|
85
217
|
});
|
|
86
218
|
|
|
87
|
-
|
|
219
|
+
todo.title = "Learn r-state-tree";
|
|
88
220
|
stop();
|
|
89
221
|
```
|
|
90
222
|
|
|
223
|
+
### Model decorators
|
|
224
|
+
|
|
225
|
+
Use decorators to configure model properties:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import {
|
|
229
|
+
Model,
|
|
230
|
+
state,
|
|
231
|
+
identifier,
|
|
232
|
+
child,
|
|
233
|
+
children,
|
|
234
|
+
modelRef,
|
|
235
|
+
modelRefs,
|
|
236
|
+
} from "r-state-tree";
|
|
237
|
+
|
|
238
|
+
class User extends Model {
|
|
239
|
+
@identifier id = 0;
|
|
240
|
+
@state name = "";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
class TodoModel extends Model {
|
|
244
|
+
@identifier id = 0;
|
|
245
|
+
@state title = "";
|
|
246
|
+
@modelRef assignee?: User; // Reference to another model by ID
|
|
247
|
+
@child metadata = MetadataModel.create(); // Nested child model
|
|
248
|
+
@children tags: TagModel[] = []; // Array of child models
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Model references
|
|
253
|
+
|
|
254
|
+
Reference models by ID using `@modelRef` and `@modelRefs`:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
class ProjectModel extends Model {
|
|
258
|
+
@identifier id = 0;
|
|
259
|
+
@children users: User[] = [];
|
|
260
|
+
@modelRef owner?: User;
|
|
261
|
+
|
|
262
|
+
assignOwner(userId: number) {
|
|
263
|
+
// Find user by ID and set as owner
|
|
264
|
+
const user = this.users.find((u) => u.id === userId);
|
|
265
|
+
this.owner = user;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const project = ProjectModel.create({
|
|
270
|
+
id: 1,
|
|
271
|
+
users: [
|
|
272
|
+
{ id: 1, name: "Alice" },
|
|
273
|
+
{ id: 2, name: "Bob" },
|
|
274
|
+
],
|
|
275
|
+
owner: { id: 1 }, // Reference by ID in snapshot
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
project.owner?.name; // "Alice"
|
|
279
|
+
```
|
|
280
|
+
|
|
91
281
|
### Snapshot diffs
|
|
92
282
|
|
|
93
|
-
Use `onSnapshotDiff` to receive undo/redo payloads
|
|
283
|
+
Use `onSnapshotDiff` to receive undo/redo payloads:
|
|
94
284
|
|
|
95
285
|
```ts
|
|
96
|
-
const
|
|
97
|
-
|
|
286
|
+
const history: SnapshotDiff[] = [];
|
|
287
|
+
|
|
288
|
+
const off = onSnapshotDiff(todo, (diff) => {
|
|
289
|
+
history.push(diff);
|
|
98
290
|
});
|
|
291
|
+
|
|
292
|
+
todo.title = "New title";
|
|
293
|
+
todo.completed = true;
|
|
294
|
+
|
|
295
|
+
// Undo
|
|
296
|
+
applySnapshot(todo, history[history.length - 1].undo);
|
|
297
|
+
|
|
298
|
+
// Redo
|
|
299
|
+
applySnapshot(todo, history[history.length - 1].redo);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Context with Models
|
|
303
|
+
|
|
304
|
+
Models also support context:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const AuthContext = createContext<User | null>(null);
|
|
308
|
+
|
|
309
|
+
class AppModel extends Model {
|
|
310
|
+
@child currentUser = User.create({ id: 1, name: "Alice" });
|
|
311
|
+
|
|
312
|
+
[AuthContext.provide]() {
|
|
313
|
+
return this.currentUser;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@child project = ProjectModel.create();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
class ProjectModel extends Model {
|
|
320
|
+
get currentUser() {
|
|
321
|
+
return AuthContext.consume(this);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
99
324
|
```
|
|
100
325
|
|
|
101
326
|
## Testing
|