@expressive/mvc 1.0.0-beta.2 → 1.0.0-beta.23

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 CHANGED
@@ -14,22 +14,28 @@
14
14
  <p align="center">
15
15
  <a href="https://www.npmjs.com/package/@expressive/mvc"><img alt="NPM" src="https://badge.fury.io/js/%40expressive%2Fmvc.svg"></a>
16
16
  <a href=""><img alt="Build" src="https://shields-staging.herokuapp.com/npm/types/@expressive/mvc.svg"></a>
17
+ <img src="https://img.shields.io/badge/Coverage-100%25-brightgreen.svg">
17
18
  </p>
18
19
 
19
20
  <p align="center">
20
- Define classes to power UI, by extending <code>Model</code>.<br/>
21
+ Define classes to power UI by extending <code>Model</code>.<br/>
21
22
  Builtin hooks will manage renders, as needed, for any data.<br/>
22
23
  When properties change, your components will too.<br/>
23
24
  </p>
24
25
 
25
26
  <br/>
26
- <h1 id="overview-section">Overview</h1>
27
+ <p align="center">
28
+ <b>Have suggestions, need help or just want to discuss?</b><br/><br/>
29
+ <a href="https://join.slack.com/t/expressivejs/shared_invite/zt-s2j5cdhz-gffKn3bTATMbXf~iq4pvHg" alt="Join Slack">
30
+ <img src="https://img.shields.io/badge/Slack-Come%20say%20hi!-blueviolet" />
31
+ </a>
32
+ </p>
27
33
 
28
- By extending `Model`, classes can contain state and logic for components. Models are easy to define, use and even provide. They limit complexity, and help make components dumber without sacrificing features.
34
+ <br/>
35
+ <h1 id="overview-section">Overview</h1>
29
36
 
30
- When a static-methods are used in a component, an instance is found or created for its use. By watching what values are accessed on render, the hook itself will refresh and keep sync with them.
37
+ Classes which extend `Model` can manage behavior for components. Custom models are easy to define, use and even provide. While hooks are great, state and logic are not a part of well-formed JSX. Models help wrap that stuff in robust, portable controllers instead.
31
38
 
32
- This behavior combines with actions, computed properties, events, and components themselves to facilitate a **M**odel-**V**iew-**C**ontroller pattern in React.
33
39
 
34
40
  <br/>
35
41
 
@@ -50,7 +56,7 @@ class Counter extends Model {
50
56
 
51
57
  ### Step 2
52
58
 
53
- Pick a built-in hook, such as `use()`, to make your component stateful.
59
+ Pick a built-in hook, such as `use()`, to make a component stateful.
54
60
 
55
61
  ```jsx
56
62
  const KitchenCounter = () => {
@@ -70,10 +76,10 @@ const KitchenCounter = () => {
70
76
  [See it in action](https://codesandbox.io/s/example-counter-th8xl) 🚀 You've already got something usable!
71
77
 
72
78
  <br/>
73
- <h1 id="good-start">Things you can do</h1>
79
+ <h2 id="good-start">Things you can do</h2>
74
80
 
75
- ### Combine any number of `useState` values
76
- Place any data in your model. Updating any value will trigger a render.
81
+ ### Track any number of values
82
+ Updating any of them will trigger a render.
77
83
 
78
84
  ```jsx
79
85
  class State extends Model {
@@ -88,9 +94,10 @@ const MyComponent = () => {
88
94
  return <div>...</div>
89
95
  }
90
96
  ```
97
+ <br/>
91
98
 
92
99
  ### Refresh using simple assignment
93
- Reserved `set` loops back to instance, to update from inside a component.
100
+ Reserved `set` loops back to the instance, to update from inside a component.
94
101
 
95
102
  ```jsx
96
103
  class Hello extends Model {
@@ -107,36 +114,36 @@ const MyComponent = () => {
107
114
  )
108
115
  }
109
116
  ```
117
+ <br/>
110
118
 
111
119
  ### Share state via context
112
- Models can provide and consume state with static methods. Classes themselves act as a key.
120
+ Provide and consume with simple methods. Classes are their own key.
113
121
 
114
122
  ```jsx
115
123
  import Model, { Provider } from "@expressive/mvc";
116
124
 
117
125
  class Shared extends Model {
118
- foo = 1;
119
- bar = 2;
126
+ value = 1;
120
127
  }
121
128
 
122
129
  const Parent = () => (
123
130
  <Provider of={Shared}>
124
- <Foo />
125
- <Bar />
131
+ <Child />
126
132
  </Provider>
127
133
  )
128
134
 
129
- const Foo = () => {
130
- const { foo } = Shared.tap();
135
+ const Child = () => {
136
+ const { value } = Shared.get();
131
137
 
132
138
  return (
133
- <p>Value of shared foo is {foo}!</p>
139
+ <p>Shared value is: {value}!</p>
134
140
  )
135
141
  }
136
142
  ```
143
+ <br/>
137
144
 
138
- ### Respond to component lifecycle
139
- Hooks are aware of lifecycle and will call method-handlers you define.
145
+ ### Respond to lifecycle
146
+ Hooks aware of lifecycle will call method-handlers you define.
140
147
 
141
148
  ```jsx
142
149
  class Timer extends Model {
@@ -145,7 +152,6 @@ class Timer extends Model {
145
152
 
146
153
  componentDidMount(){
147
154
  const inc = () => this.elapsed++;
148
-
149
155
  this.interval = setInterval(inc, 1000);
150
156
  }
151
157
 
@@ -157,20 +163,21 @@ class Timer extends Model {
157
163
  const MyTimer = () => {
158
164
  const { elapsed } = Timer.use();
159
165
 
160
- return <pre>I've existed for { elapsed } seconds!</pre>;
166
+ return <p>I've existed for { elapsed } seconds!</p>;
161
167
  }
162
168
  ```
169
+ <br/>
163
170
 
164
171
  ### Control components with `async`
165
172
 
166
173
  ```jsx
167
174
  class Greetings extends Model {
168
175
  response = undefined;
169
- thinking = false;
176
+ waiting = false;
170
177
  error = false;
171
178
 
172
179
  sayHello = async () => {
173
- this.thinking = true;
180
+ this.waiting = true;
174
181
 
175
182
  try {
176
183
  const res = await fetch("https://my.api/hello");
@@ -183,15 +190,15 @@ class Greetings extends Model {
183
190
  }
184
191
 
185
192
  const MyComponent = () => {
186
- const { error, response, thinking, sayHello } = Greetings.use();
193
+ const { error, response, waiting, sayHello } = Greetings.use();
187
194
 
188
195
  if(response)
189
196
  return <p>Server said: {response}</p>
190
197
 
191
198
  if(error)
192
- return <p>There was an issue saying hello :(</p>
199
+ return <p>There was an issue saying hello</p>
193
200
 
194
- if(thinking)
201
+ if(waiting)
195
202
  return <p>Sent! Waiting on response...</p>
196
203
 
197
204
  return (
@@ -199,14 +206,17 @@ const MyComponent = () => {
199
206
  )
200
207
  }
201
208
  ```
209
+ <br/>
202
210
 
203
- ### Extend and reuse logic
204
- Custom models can be extended, to add features or to set required properties. This makes reusable logic is easy to store, document and share.
211
+ ### Extend to configure
212
+ Use class extension to set needed properties.
213
+ Reusable logic is easy to implement, document and share.
205
214
 
206
215
  ```ts
207
216
  abstract class About extends Model {
208
217
  abstract name: string;
209
218
  abstract birthday: Date;
219
+ abstract wants: any;
210
220
 
211
221
  happyBirthday(){
212
222
  // ...
@@ -216,11 +226,13 @@ abstract class About extends Model {
216
226
  class AboutMe extends About {
217
227
  name = "John Doe";
218
228
  birthday = new Date("January 1");
219
- wants = "PS5";
229
+ wants = "a PS5";
220
230
  }
221
231
  ```
222
232
 
223
- #### ... and plenty more.
233
+ </br>
234
+
235
+ #### And you can do more, all with type safety and code-assist, out of the box.
224
236
 
225
237
  </br>
226
238
 
@@ -243,9 +255,9 @@ class AboutMe extends About {
243
255
  &ensp; •&nbsp; [Lifecycle](#concept-lifecycle) <br/>
244
256
  &ensp; •&nbsp; [Events](#concept-events) <br/>
245
257
  &ensp;&ensp;&ensp; ◦&nbsp; [Listening](#concept-listen-event) <br/>
246
- &ensp;&ensp;&ensp; ◦&nbsp; [Dispatching](#concept-push-event) <br/>
258
+ &ensp;&ensp;&ensp; ◦&nbsp; [Dispatch](#concept-push-event) <br/>
247
259
  &ensp;&ensp;&ensp; ◦&nbsp; [Built-in](#concept-builtin-events) <br/>
248
- &ensp; •&nbsp; [Monitored Externals](#concept-external) <br/>
260
+ &ensp; •&nbsp; [Monitored Props](#concept-external) <br/>
249
261
  &ensp; •&nbsp; [Async and callbacks](#concept-async) <br/>
250
262
 
251
263
  **Shared State** <br/>
@@ -262,10 +274,10 @@ class AboutMe extends About {
262
274
  &ensp;&ensp;&ensp; ◦&nbsp; [`get`](#method-get) <br/>
263
275
  &ensp;&ensp;&ensp; ◦&nbsp; [`tap`](#method-tap) <br/>
264
276
  &ensp; •&nbsp; [Consumer](#concept-consumer) <br/>
265
- &ensp;&ensp;&ensp; ◦&nbsp; [render](#consumer-render) <br/>
266
- &ensp;&ensp;&ensp; ◦&nbsp; [`get` prop](#consumer-get) <br/>
267
- &ensp;&ensp;&ensp; ◦&nbsp; [`tap` prop](#consumer-tap) <br/>
268
- &ensp;&ensp;&ensp; ◦&nbsp; [`has` prop](#consumer-has) <br/>
277
+ &ensp;&ensp;&ensp; ◦&nbsp; [children](#consumer-render) <br/>
278
+ &ensp;&ensp;&ensp; ◦&nbsp; [`get`](#consumer-get) <br/>
279
+ &ensp;&ensp;&ensp; ◦&nbsp; [`tap`](#consumer-tap) <br/>
280
+ &ensp;&ensp;&ensp; ◦&nbsp; [`has`](#consumer-has) <br/>
269
281
 
270
282
  **Composition** <br/>
271
283
  &ensp; •&nbsp; [Simple Composition](#concept-compose) <br/>
@@ -288,11 +300,11 @@ class AboutMe extends About {
288
300
  &ensp; •&nbsp; [Auto Debounce](#concept-debounce) <br/>
289
301
  <!-- &ensp; •&nbsp; [Selectors](#concept-selectors) <br/> -->
290
302
 
291
- ### API
303
+ <!-- ### API
292
304
  &ensp; •&nbsp; [Model](#api-controller) <br/>
293
305
  &ensp; •&nbsp; [Singleton](#api-singleton) <br/>
294
306
  &ensp; •&nbsp; [Reserved](#api-reserved) <br/>
295
- &ensp; •&nbsp; [Lifecycle](#api-lifecycle) <br/>
307
+ &ensp; •&nbsp; [Lifecycle](#api-lifecycle) <br/> -->
296
308
 
297
309
  <br/>
298
310
 
@@ -315,23 +327,22 @@ import Model from "@expressive/mvc";
315
327
  Ultimately, the workflow is simple.
316
328
 
317
329
  1. Create a class. Fill it with the values, getters, and methods you'll need.
318
- 2. Extend `Model` (or any derivative, for that matter) to make it reactive.
319
- 3. Within a component, use built-in methods as you would any normal hook.
320
- 4. Destructure the values, used by a component, to subscribe.
330
+ 2. Extend `Model` (or any derivative, for that matter), making it reactive.
331
+ 3. Within a component, use built-in methods as you would normal hooks.
332
+ 4. Destructure out the values used by a component, to then subscribe.
321
333
  5. Update those values on demand. Component will sync automagically. ✨
322
334
  <br/>
323
335
 
324
- ### Glossary
336
+ > You may recognize this as similar to approach pioneered by [MobX](https://mobx.js.org/README.html). Here though, we avoid using decorators and other boilerplate.
325
337
 
326
- The following is a crash-course to help get you up to speed.<br/>
327
- Here's some library jargon which will be good to know.
338
+ ### Glossary
328
339
 
329
- - **Model**: Any class you'll write extending `Model`. Your definition for a type of state.
330
- - **Controller**: An instance of your model, containing its state and behavior.
331
- - **State**: The values tracked by a controller, as defined by the model.
332
- - **Subscriber**: A hook, or otherwise special callback, responding to updates.
333
- - **View**: A function-component which can accept hooks, to contain some controller.
334
- - **Element**: Instance of a component/view, actively mounted with its own state and lifecycle.
340
+ - **Model**: Any class you'll write extending `Model`. Defines a type of state.
341
+ - **Controller**: An instance of Model, containing its state and behavior.
342
+ - **State**: The values managed by a controller, as defined by the model.
343
+ - **Subscriber**: A hook, or otherwise a callback, responding to updates.
344
+ - **View**: A function-component which accepts hooks, to contain a controller.
345
+ - **Element**: Instance of a component mounted, having state and lifecycle.
335
346
 
336
347
  <br/>
337
348
  <h2 id="concept-simple">First Steps</h2>
@@ -495,10 +506,10 @@ class Timer extends Model {
495
506
 
496
507
  A getter's value is cached, facing the user. It will only run when a dependency changes, and **not upon access** as you might expect. However, getters are also lazy, so first access is one exception.
497
508
 
498
- Because getters run whenever a controller thinks they *could* change, make sure to design them along these principles:
509
+ Getters run whenever a controller thinks they *could* change. Make sure to design them along these lines:
499
510
  - Getters should be deterministic. Only expect a change where inputs have changed.
500
511
  - Avoid computing from values which change a lot, but don't change output as often.
501
- - [Goes without saying](https://www.youtube.com/watch?v=0i0IlSKn0sE "Goes Without Saying") but, **side-effects are an anti-pattern** and can cause infinite loops.
512
+ - [Goes without saying](https://www.youtube.com/watch?v=0i0IlSKn0sE "Goes Without Saying") but, **side-effects are an anti-pattern** and may cause infinite loops.
502
513
 
503
514
  <br/>
504
515
 
@@ -534,7 +545,7 @@ const SayHello = ({ fullName }) => {
534
545
 
535
546
  <h2 id="concept-passing-props">Assigning props to state</h2>
536
547
 
537
- Besides `use`, similar methods will assign values after a controller is created. This is a great alternative to a manual setup, as done previously.
548
+ Besides `use`, similar methods will assign values after a controller is created. Good alternative to a manual setup.
538
549
 
539
550
  <h4 id="method-uses"><code>Model.uses(source, keys?)</code></h4>
540
551
 
@@ -622,7 +633,6 @@ class TimerControl extends Model {
622
633
 
623
634
  componentDidMount(){
624
635
  const inc = () => this.elapsed++;
625
-
626
636
  this.interval = setInterval(inc, 1000);
627
637
  }
628
638
 
@@ -655,7 +665,7 @@ Besides state change, what a subscriber really cares about is events. *Updates a
655
665
 
656
666
  <br />
657
667
 
658
- <h3 id="concept-listen-event">Listening for events manually</h3>
668
+ <h2 id="concept-listen-event">Listening for events manually</h2>
659
669
 
660
670
  ```js
661
671
  const normalCallback = (value, key) => {
@@ -684,7 +694,7 @@ Will also expire when triggered. May be cancelled with the returned callback.
684
694
  If `callback` is not provided, `once` will return a Promise of keys watched/updated.
685
695
 
686
696
  <br />
687
- <h3 id="concept-listen-effect">Define an effect for one or more values</h3>
697
+ <h2 id="concept-listen-effect">Defining an effect</h2>
688
698
 
689
699
  ```js
690
700
  function effectCallback(state){
@@ -715,30 +725,31 @@ function subscribedCallback(state){
715
725
  ```
716
726
  #### `state.effect(subscribedCallback) => remove`
717
727
 
718
- If no explicit `keys` are given, effect callback will self-subscribe. Just like a hook, it will detect values used and automatically update for new ones.
728
+ If no explicit `keys` are given, the effect callback will self-subscribe. Just like a hook, it detects values used and automatically will update for new ones.
719
729
 
720
- > Note: In order to scan for property access, the effect will always run at-least once.
730
+ > Note: In order to scan for property access, this effect will run at-least once, immediately.
721
731
 
722
732
  <br />
723
- <h3 id="concept-push-event">Pushing your own events</h3>
733
+ <h2 id="concept-push-event">Pushing your own events</h2>
724
734
 
725
735
  #### `state.update(key)`
726
736
 
727
737
  Fires a synthetic event, sent to all listeners for key(s) selected, be them components or callback-listeners above. Any `string` or `symbol` may be a key. Naturally, a key which is defined on state already, will be treated as an update.
728
738
 
729
739
  > Events make it easier to design around closures and callbacks, keeping as few things on your model as possible. Event methods may also be used externally, for other code to interact with.
730
- <br /><br />
731
740
 
732
- <h3 id="concept-builtin-event">Listening for built-in events</h3>
741
+ <br />
742
+ <h2 id="concept-builtin-event">Listening for built-in events</h2>
733
743
 
734
744
  Controllers will emit lifecycle events for a bound component (depending on the hook).
735
745
 
736
746
  > Often lifecycle is critical to a controller's correct behavior. While we do have lifecycle-methods, it's recommended to use events where able. This way, if your class is extended and redefines a handler, yours is not at the mercy of a `super[event]()` call.
737
747
 
738
748
  Lifecycle events share names with their respective methods, [listed here](#lifecycle-api).
739
- <br /><br />
740
749
 
741
- ### Event handling in-practice:
750
+ <br />
751
+
752
+ ## Event handling in-practice:
742
753
 
743
754
  ```js
744
755
  class TickTockClock extends Model {
@@ -796,8 +807,6 @@ Sometimes, we want to react to changes coming from outside, usually via props. O
796
807
 
797
808
  This method helps watch an object by running [`assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) on it *every render*. Because controllers only react to new values, this makes for a simple way to watch externals. Combine this with getters and event-listeners, to do things when inputs change.
798
809
 
799
- <br />
800
-
801
810
  ```ts
802
811
  class ActivityTracker extends Model {
803
812
  active = undefined;
@@ -842,8 +851,6 @@ const Activate = () => {
842
851
 
843
852
  <sup><a href="https://codesandbox.io/s/example-using-method-wvrtu">View in CodeSandbox</a></sup>
844
853
 
845
- With this, we can interact with all different sorts of state!
846
-
847
854
  > **Note:** Method is also picky (ala `uses`), and will ignore values which don't already exist in state.
848
855
 
849
856
  <br />
@@ -1012,7 +1019,6 @@ export class FooBar extends Model {
1012
1019
  > Models and their consumers do not need to live in same file, let alone module. Want to interact with another library's state? This can make for a pro-consumer solution. ☝️😑
1013
1020
 
1014
1021
  <br/>
1015
-
1016
1022
  <h2 id="concept-provider">Providing an instance</h2>
1017
1023
 
1018
1024
  Here, pass a controller to the `of` prop. For any children, it will be made accessible via its class.
@@ -1035,6 +1041,7 @@ export const App = () => {
1035
1041
  }
1036
1042
  ```
1037
1043
 
1044
+ <br/>
1038
1045
  <h2 id="concept-provider-spawning">Spawning an instance</h2>
1039
1046
 
1040
1047
  Without constructor-arguments, creating the instance separately can be an unnecessary step. Pass a Model itself, to both create and provide a state in one sitting.
@@ -1049,6 +1056,7 @@ export const App = () => {
1049
1056
  }
1050
1057
  ```
1051
1058
 
1059
+ <br/>
1052
1060
  <h2 id="concept-provider-props">Spawning with props</h2>
1053
1061
 
1054
1062
  When a Model is passed to `of` directly, the Provider will behave similarly to [`Model.using()`](#concept-external). All other props are forwarded to state and likewise observed.
@@ -1065,6 +1073,7 @@ const FancyFooBar = (props) => {
1065
1073
 
1066
1074
  > Now, consumers can see incoming-data, which only the parent has access to!
1067
1075
 
1076
+ <br/>
1068
1077
  <h2 id="concept-provider-multi">Providing Multiple</h2>
1069
1078
 
1070
1079
  Finally, `of` can accept a collection of models and/or state objects. Mix and match as needed to ensure a DRY, readible root.
@@ -1191,7 +1200,7 @@ export class FooBar extends Model {
1191
1200
  ```
1192
1201
 
1193
1202
  <br/>
1194
- <h2 id="concept-consumer-hooks">Hooks Methods</h2>
1203
+ <h2 id="concept-consumer-hooks">Method Hooks</h2>
1195
1204
 
1196
1205
  Models make fetching a state super easy. On every model, there are defined three `useContext` like methods. They find, return and maintain the nearest instance of class they belong to.
1197
1206
 
@@ -1296,7 +1305,7 @@ const LazyConsumer = () => {
1296
1305
  <br />
1297
1306
  <h2 id="concept-ambient">Peer Consumers</h2>
1298
1307
 
1299
- While context is great for components, controllers can use it too, that of the component where used. This is helped by importing a helper function, likewise called `tap`.
1308
+ While context is great for components, controllers can use it too, that of the component where used. This is helped by importing a special function, likewise called `tap`.
1300
1309
  > [This is an instruction](#concept-instruction), in-short a factory function. It tells the controller while initializing: the defined property is special, run some logic to set that up.
1301
1310
 
1302
1311
  On your Model, assign a property with the value of `tap(Model)`, passing in the type of state you're interested in.
@@ -1320,7 +1329,7 @@ class Greet extends Model {
1320
1329
  }
1321
1330
  }
1322
1331
  ```
1323
- Create a controller which taps another one. Make sure it's wrapped by proper `Provider`, and controller will fetch that instance from context.
1332
+ Create a controller which taps another one. Make sure it's wrapped by proper `Provider`, and the controller will fetch that instance from context.
1324
1333
  ```js
1325
1334
  import { Provider } from "@expressive/mvc";
1326
1335
 
@@ -1409,8 +1418,8 @@ class FooBar extends Model {
1409
1418
  return this.foo.value + this.bar.value;
1410
1419
  }
1411
1420
 
1412
- plusFoo = () => this.bar.value = this.value;
1413
- plusBar = () => this.foo.value = this.value;
1421
+ addFoo = () => this.bar.value = this.value;
1422
+ addBar = () => this.foo.value = this.value;
1414
1423
  }
1415
1424
  ```
1416
1425
 
@@ -1418,17 +1427,17 @@ class FooBar extends Model {
1418
1427
 
1419
1428
  ```js
1420
1429
  const FibonacciApp = () => {
1421
- const { foo, bar, value, plusFoo, plusBar } = FooBar.use();
1430
+ const { foo, bar, value, addFoo, addBar } = FooBar.use();
1422
1431
 
1423
1432
  return (
1424
1433
  <div>
1425
1434
  <div>
1426
1435
  Foo + Bar = {value}
1427
1436
  </div>
1428
- <div onClick={plusBar}>
1437
+ <div onClick={addBar}>
1429
1438
  Foo's value is ${foo.value}, click to add in bar!
1430
1439
  </div>
1431
- <div onClick={plusFoo}>
1440
+ <div onClick={addFoo}>
1432
1441
  Bar's value is ${bar.value}, click to add in foo!
1433
1442
  </div>
1434
1443
  </div>
@@ -1466,7 +1475,6 @@ class Dependant extends Model {
1466
1475
 
1467
1476
  > Here we've learned how Models allow controllers to cooperate and play off of eachother. They allow us to break-up behavior into smaller pieces, building-blocks if you will. With that, we have an easy way to separate concerns, freeing up focus for more added nuance and features.
1468
1477
 
1469
- <br/>
1470
1478
  <br/>
1471
1479
 
1472
1480
  ### :construction: More Docs are on the way! 🏗