mobx-mantle 0.1.5
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/LICENSE +21 -0
- package/README.md +613 -0
- package/dist/index.cjs +285 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +143 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +253 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Craig
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
# mobx-mantle
|
|
2
|
+
|
|
3
|
+
A minimal library that brings MobX reactivity to React components with a familiar class-based API.
|
|
4
|
+
|
|
5
|
+
Full access to the React ecosystem. Better access to vanilla JS libraries. Simpler DX for both.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
React hooks solve real problems—stale closures, dependency tracking, memoization. MobX already solves those problems. So if you're using MobX, hooks add complexity without benefit.
|
|
10
|
+
|
|
11
|
+
This library lets you write components in a way that is more familiar to common programming patterns outside the React ecosystem: mutable state, stable references, computed getters, direct method calls.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install mobx-mantle
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires React 17+, MobX 6+, and mobx-react-lite 3+.
|
|
20
|
+
|
|
21
|
+
## Basic Example
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { View, createView } from 'mobx-mantle';
|
|
25
|
+
|
|
26
|
+
interface CounterProps {
|
|
27
|
+
initial: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class CounterView extends View<CounterProps> {
|
|
31
|
+
count = 0;
|
|
32
|
+
|
|
33
|
+
onCreate() {
|
|
34
|
+
this.count = this.props.initial;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
increment() {
|
|
38
|
+
this.count++;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
render() {
|
|
42
|
+
return (
|
|
43
|
+
<button onClick={this.increment}>
|
|
44
|
+
Count: {this.count}
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Counter = createView(CounterView);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## What You Get
|
|
54
|
+
|
|
55
|
+
**Direct mutation:**
|
|
56
|
+
```tsx
|
|
57
|
+
this.items.push(item); // not setItems(prev => [...prev, item])
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Computed values via getters:**
|
|
61
|
+
```tsx
|
|
62
|
+
get completed() { // not useMemo(() => items.filter(...), [items])
|
|
63
|
+
return this.items.filter(i => i.done);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Stable methods (auto-bound):**
|
|
68
|
+
```tsx
|
|
69
|
+
toggle(id: number) { // automatically bound to this
|
|
70
|
+
const item = this.items.find(i => i.id === id);
|
|
71
|
+
if (item) item.done = !item.done;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// use directly, no wrapper needed
|
|
75
|
+
<button onClick={this.toggle} />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**React to changes explicitly:**
|
|
79
|
+
```tsx
|
|
80
|
+
onMount() {
|
|
81
|
+
return reaction(
|
|
82
|
+
() => this.props.filter,
|
|
83
|
+
(filter) => this.applyFilter(filter)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Lifecycle
|
|
89
|
+
|
|
90
|
+
| Method | When |
|
|
91
|
+
|--------|------|
|
|
92
|
+
| `onCreate()` | Instance created, props available |
|
|
93
|
+
| `onLayoutMount()` | DOM ready, before paint. Return a cleanup function (optional). |
|
|
94
|
+
| `onMount()` | Component mounted, after paint. Return a cleanup function (optional). |
|
|
95
|
+
| `onUnmount()` | Component unmounting. Called after cleanups (optional). |
|
|
96
|
+
| `render()` | On mount and updates. Return JSX. |
|
|
97
|
+
|
|
98
|
+
### Props Reactivity
|
|
99
|
+
|
|
100
|
+
`this.props` is reactive—your component re-renders when accessed props change. Use `reaction` or `autorun` to respond to prop changes:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
onMount() {
|
|
104
|
+
return reaction(
|
|
105
|
+
() => this.props.filter,
|
|
106
|
+
(filter) => this.applyFilter(filter)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or access props directly in `render()` and MobX handles re-renders when they change.
|
|
112
|
+
|
|
113
|
+
## Patterns
|
|
114
|
+
|
|
115
|
+
### Combined (default)
|
|
116
|
+
|
|
117
|
+
State, logic, and template in one class:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
class TodoView extends View<Props> {
|
|
121
|
+
todos: TodoItem[] = [];
|
|
122
|
+
input = '';
|
|
123
|
+
|
|
124
|
+
add() {
|
|
125
|
+
this.todos.push({ id: Date.now(), text: this.input, done: false });
|
|
126
|
+
this.input = '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
setInput(e: React.ChangeEvent<HTMLInputElement>) {
|
|
130
|
+
this.input = e.target.value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
render() {
|
|
134
|
+
return (
|
|
135
|
+
<div>
|
|
136
|
+
<input value={this.input} onChange={this.setInput} />
|
|
137
|
+
<button onClick={this.add}>Add</button>
|
|
138
|
+
<ul>{this.todos.map(t => <li key={t.id}>{t.text}</li>)}</ul>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const Todo = createView(TodoView);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Separated
|
|
148
|
+
|
|
149
|
+
ViewModel and template separate:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import { ViewModel, createView } from 'mobx-mantle';
|
|
153
|
+
|
|
154
|
+
class TodoVM extends ViewModel<Props> {
|
|
155
|
+
todos: TodoItem[] = [];
|
|
156
|
+
input = '';
|
|
157
|
+
|
|
158
|
+
add() {
|
|
159
|
+
this.todos.push({ id: Date.now(), text: this.input, done: false });
|
|
160
|
+
this.input = '';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
setInput(e: React.ChangeEvent<HTMLInputElement>) {
|
|
164
|
+
this.input = e.target.value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const Todo = createView(TodoVM, (vm) => (
|
|
169
|
+
<div>
|
|
170
|
+
<input value={vm.input} onChange={vm.setInput} />
|
|
171
|
+
<button onClick={vm.add}>Add</button>
|
|
172
|
+
<ul>{vm.todos.map(t => <li key={t.id}>{t.text}</li>)}</ul>
|
|
173
|
+
</div>
|
|
174
|
+
));
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### With Decorators
|
|
178
|
+
|
|
179
|
+
For teams that prefer explicit annotations, disable `autoObservable` globally:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
// app.tsx (or entry point)
|
|
183
|
+
import { configure } from 'mobx-mantle';
|
|
184
|
+
|
|
185
|
+
configure({ autoObservable: false });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**TC39 Decorators** (recommended, self-registering):
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
class TodoView extends View<Props> {
|
|
192
|
+
@observable accessor todos: TodoItem[] = [];
|
|
193
|
+
@observable accessor input = '';
|
|
194
|
+
|
|
195
|
+
@action add() {
|
|
196
|
+
this.todos.push({ id: Date.now(), text: this.input, done: false });
|
|
197
|
+
this.input = '';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
render() {
|
|
201
|
+
return /* ... */;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export const Todo = createView(TodoView);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Legacy Decorators** (experimental, requires `makeObservable`):
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
class TodoView extends View<Props> {
|
|
212
|
+
@observable todos: TodoItem[] = [];
|
|
213
|
+
@observable input = '';
|
|
214
|
+
|
|
215
|
+
@action add() {
|
|
216
|
+
this.todos.push({ id: Date.now(), text: this.input, done: false });
|
|
217
|
+
this.input = '';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
render() {
|
|
221
|
+
return /* ... */;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const Todo = createView(TodoView);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Note: `this.props` is always reactive regardless of decorator type.
|
|
229
|
+
|
|
230
|
+
## Refs
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
class FormView extends View<Props> {
|
|
234
|
+
inputRef = this.ref<HTMLInputElement>();
|
|
235
|
+
|
|
236
|
+
onMount() {
|
|
237
|
+
this.inputRef.current?.focus();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
render() {
|
|
241
|
+
return <input ref={this.inputRef} />;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Forwarding Refs
|
|
247
|
+
|
|
248
|
+
Expose a DOM element to parent components via `this.forwardRef`:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
class FancyInputView extends View<InputProps> {
|
|
252
|
+
render() {
|
|
253
|
+
return <input ref={this.forwardRef} className="fancy-input" />;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const FancyInput = createView(FancyInputView);
|
|
258
|
+
|
|
259
|
+
// Parent can now get a ref to the underlying input:
|
|
260
|
+
function Parent() {
|
|
261
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<>
|
|
265
|
+
<FancyInput ref={inputRef} placeholder="Type here..." />
|
|
266
|
+
<button onClick={() => inputRef.current?.focus()}>Focus</button>
|
|
267
|
+
</>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Reactions
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
class SearchView extends View<Props> {
|
|
276
|
+
query = '';
|
|
277
|
+
results: string[] = [];
|
|
278
|
+
|
|
279
|
+
onMount() {
|
|
280
|
+
const dispose = reaction(
|
|
281
|
+
() => this.query,
|
|
282
|
+
async (query) => {
|
|
283
|
+
if (query.length > 2) {
|
|
284
|
+
this.results = await searchApi(query);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
{ delay: 300 }
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
return dispose;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
setQuery(e: React.ChangeEvent<HTMLInputElement>) {
|
|
294
|
+
this.query = e.target.value;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
render() {
|
|
298
|
+
return (
|
|
299
|
+
<div>
|
|
300
|
+
<input value={this.query} onChange={this.setQuery} />
|
|
301
|
+
<ul>{this.results.map(r => <li key={r}>{r}</li>)}</ul>
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## React Hooks
|
|
309
|
+
|
|
310
|
+
Hooks work inside `render()`:
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
class DataView extends View<{ id: string }> {
|
|
314
|
+
render() {
|
|
315
|
+
const navigate = useNavigate();
|
|
316
|
+
const { data, isLoading } = useQuery({
|
|
317
|
+
queryKey: ['item', this.props.id],
|
|
318
|
+
queryFn: () => fetchItem(this.props.id),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (isLoading) return <div>Loading...</div>;
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<div onClick={() => navigate('/home')}>
|
|
325
|
+
{data.name}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Vanilla JS Integration
|
|
333
|
+
|
|
334
|
+
Imperative libraries become straightforward:
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
class ChartView extends View<{ data: number[] }> {
|
|
338
|
+
containerRef = this.ref<HTMLDivElement>();
|
|
339
|
+
chart: Chart | null = null;
|
|
340
|
+
|
|
341
|
+
onMount() {
|
|
342
|
+
this.chart = new Chart(this.containerRef.current!, {
|
|
343
|
+
data: this.props.data,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const dispose = reaction(
|
|
347
|
+
() => this.props.data,
|
|
348
|
+
(data) => this.chart?.update(data)
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
return () => {
|
|
352
|
+
dispose();
|
|
353
|
+
this.chart?.destroy();
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
render() {
|
|
358
|
+
return <div ref={this.containerRef} />;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Compare to hooks:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
function ChartView({ data }) {
|
|
367
|
+
const containerRef = useRef();
|
|
368
|
+
const chartRef = useRef();
|
|
369
|
+
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
chartRef.current = new Chart(containerRef.current, { data });
|
|
372
|
+
return () => chartRef.current.destroy();
|
|
373
|
+
}, []);
|
|
374
|
+
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
chartRef.current?.update(data);
|
|
377
|
+
}, [data]);
|
|
378
|
+
|
|
379
|
+
return <div ref={containerRef} />;
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Split effects, multiple refs, dependency tracking—all unnecessary with mobx-mantle.
|
|
384
|
+
|
|
385
|
+
## Behaviors
|
|
386
|
+
|
|
387
|
+
Behaviors are reusable pieces of state and logic that can be shared across views. Define them as plain classes, wrap with `createBehavior()`, and instantiate them in your Views.
|
|
388
|
+
|
|
389
|
+
### Basic Behavior
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
import { Behavior, createBehavior } from 'mobx-mantle';
|
|
393
|
+
|
|
394
|
+
class WindowSizeBehavior extends Behavior {
|
|
395
|
+
width = window.innerWidth;
|
|
396
|
+
height = window.innerHeight;
|
|
397
|
+
|
|
398
|
+
onMount() {
|
|
399
|
+
const handler = () => {
|
|
400
|
+
this.width = window.innerWidth;
|
|
401
|
+
this.height = window.innerHeight;
|
|
402
|
+
};
|
|
403
|
+
window.addEventListener('resize', handler);
|
|
404
|
+
return () => window.removeEventListener('resize', handler);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export default createBehavior(WindowSizeBehavior);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Use it in a View by instantiating it directly:
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
import WindowSizeBehavior from './WindowSizeBehavior';
|
|
415
|
+
|
|
416
|
+
class ResponsiveView extends View<Props> {
|
|
417
|
+
windowSize = new WindowSizeBehavior();
|
|
418
|
+
|
|
419
|
+
get isMobile() {
|
|
420
|
+
return this.windowSize.width < 768;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
render() {
|
|
424
|
+
return (
|
|
425
|
+
<div>
|
|
426
|
+
{this.isMobile ? <MobileLayout /> : <DesktopLayout />}
|
|
427
|
+
<p>Window: {this.windowSize.width}x{this.windowSize.height}</p>
|
|
428
|
+
</div>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export const Responsive = createView(ResponsiveView);
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Behaviors with Arguments
|
|
437
|
+
|
|
438
|
+
Pass arguments via constructor OR `onCreate()`—your choice:
|
|
439
|
+
|
|
440
|
+
**Option 1: Constructor args** (TypeScript shorthand)
|
|
441
|
+
```tsx
|
|
442
|
+
class FetchBehavior extends Behavior {
|
|
443
|
+
data: Item[] = [];
|
|
444
|
+
loading = false;
|
|
445
|
+
|
|
446
|
+
constructor(public url: string, public interval = 5000) {
|
|
447
|
+
super();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
onMount() {
|
|
451
|
+
this.fetchData();
|
|
452
|
+
const id = setInterval(() => this.fetchData(), this.interval);
|
|
453
|
+
return () => clearInterval(id);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async fetchData() {
|
|
457
|
+
this.loading = true;
|
|
458
|
+
this.data = await fetch(this.url).then(r => r.json());
|
|
459
|
+
this.loading = false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export default createBehavior(FetchBehavior);
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Option 2: onCreate args** (no constructor boilerplate)
|
|
467
|
+
```tsx
|
|
468
|
+
class FetchBehavior extends Behavior {
|
|
469
|
+
url!: string;
|
|
470
|
+
interval = 5000;
|
|
471
|
+
data: Item[] = [];
|
|
472
|
+
loading = false;
|
|
473
|
+
|
|
474
|
+
onCreate(url: string, interval = 5000) {
|
|
475
|
+
this.url = url;
|
|
476
|
+
this.interval = interval;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
onMount() {
|
|
480
|
+
this.fetchData();
|
|
481
|
+
const id = setInterval(() => this.fetchData(), this.interval);
|
|
482
|
+
return () => clearInterval(id);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async fetchData() {
|
|
486
|
+
this.loading = true;
|
|
487
|
+
this.data = await fetch(this.url).then(r => r.json());
|
|
488
|
+
this.loading = false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export default createBehavior(FetchBehavior);
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
```tsx
|
|
496
|
+
// Constructor args come from onCreate signature
|
|
497
|
+
class MyView extends View<Props> {
|
|
498
|
+
users = new FetchBehavior('/api/users', 10000);
|
|
499
|
+
posts = new FetchBehavior('/api/posts'); // interval defaults to 5000
|
|
500
|
+
|
|
501
|
+
render() {
|
|
502
|
+
return (
|
|
503
|
+
<div>
|
|
504
|
+
{this.users.loading ? 'Loading...' : `${this.users.data.length} users`}
|
|
505
|
+
</div>
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Behavior Lifecycle
|
|
512
|
+
|
|
513
|
+
Behaviors support the same lifecycle methods as Views:
|
|
514
|
+
|
|
515
|
+
| Method | When |
|
|
516
|
+
|--------|------|
|
|
517
|
+
| `onCreate(...args)` | Called during construction with the constructor arguments |
|
|
518
|
+
| `onLayoutMount()` | Called when parent View layout mounts (before paint). Return cleanup (optional). |
|
|
519
|
+
| `onMount()` | Called when parent View mounts (after paint). Return cleanup (optional). |
|
|
520
|
+
| `onUnmount()` | Called when parent View unmounts, after cleanups (optional). |
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
## API
|
|
524
|
+
|
|
525
|
+
### `configure(config)`
|
|
526
|
+
|
|
527
|
+
Set global defaults for all views. Settings can still be overridden per-view in `createView` options.
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
import { configure } from 'mobx-mantle';
|
|
531
|
+
|
|
532
|
+
// Disable auto-observable globally (for decorator users)
|
|
533
|
+
configure({ autoObservable: false });
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
| Option | Default | Description |
|
|
537
|
+
|--------|---------|-------------|
|
|
538
|
+
| `autoObservable` | `true` | Whether to automatically make View instances observable |
|
|
539
|
+
|
|
540
|
+
### `View<P>` / `ViewModel<P>`
|
|
541
|
+
|
|
542
|
+
Base class for view components. `ViewModel` is an alias for `View`—use it when separating the ViewModel from the template for semantic clarity.
|
|
543
|
+
|
|
544
|
+
| Property/Method | Description |
|
|
545
|
+
|-----------------|-------------|
|
|
546
|
+
| `props` | Current props (reactive) |
|
|
547
|
+
| `forwardRef` | Ref passed from parent component (for ref forwarding) |
|
|
548
|
+
| `onCreate()` | Called when instance created |
|
|
549
|
+
| `onLayoutMount()` | Called before paint, return cleanup (optional) |
|
|
550
|
+
| `onMount()` | Called after paint, return cleanup (optional) |
|
|
551
|
+
| `onUnmount()` | Called on unmount, after cleanups (optional) |
|
|
552
|
+
| `render()` | Return JSX (optional if using template) |
|
|
553
|
+
| `ref<T>()` | Create a ref for DOM elements |
|
|
554
|
+
|
|
555
|
+
### `Behavior`
|
|
556
|
+
|
|
557
|
+
Base class for behaviors. Extend it and wrap with `createBehavior()`.
|
|
558
|
+
|
|
559
|
+
| Method | Description |
|
|
560
|
+
|--------|-------------|
|
|
561
|
+
| `onCreate(...args)` | Called during construction with constructor args |
|
|
562
|
+
| `onLayoutMount()` | Called before paint, return cleanup (optional) |
|
|
563
|
+
| `onMount()` | Called after paint, return cleanup (optional) |
|
|
564
|
+
| `onUnmount()` | Called when parent View unmounts |
|
|
565
|
+
|
|
566
|
+
### `createBehavior(Class)`
|
|
567
|
+
|
|
568
|
+
Wraps a behavior class for automatic observable wrapping and lifecycle management.
|
|
569
|
+
|
|
570
|
+
```tsx
|
|
571
|
+
// Constructor args
|
|
572
|
+
class MyBehavior extends Behavior {
|
|
573
|
+
constructor(public value: string) { super(); }
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// OR onCreate args
|
|
577
|
+
class MyBehavior extends Behavior {
|
|
578
|
+
value!: string;
|
|
579
|
+
onCreate(value: string) { this.value = value; }
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export default createBehavior(MyBehavior);
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### `createView(ViewClass, templateOrOptions?)`
|
|
586
|
+
|
|
587
|
+
Creates a React component from a View class.
|
|
588
|
+
|
|
589
|
+
```tsx
|
|
590
|
+
// Basic
|
|
591
|
+
createView(MyView)
|
|
592
|
+
|
|
593
|
+
// With template
|
|
594
|
+
createView(MyView, (vm) => <div>{vm.value}</div>)
|
|
595
|
+
|
|
596
|
+
// With options
|
|
597
|
+
createView(MyView, { autoObservable: false })
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
| Option | Default | Description |
|
|
601
|
+
|--------|---------|-------------|
|
|
602
|
+
| `autoObservable` | `true` | Use `makeAutoObservable`. Set to `false` for decorators. |
|
|
603
|
+
|
|
604
|
+
## Who This Is For
|
|
605
|
+
|
|
606
|
+
- Teams using MobX for state management
|
|
607
|
+
- Developers from other platforms (mobile, backend, other frameworks)
|
|
608
|
+
- Projects integrating vanilla JS libraries
|
|
609
|
+
- Anyone tired of dependency arrays
|
|
610
|
+
|
|
611
|
+
## License
|
|
612
|
+
|
|
613
|
+
MIT
|