react-elmish 3.0.0 → 3.3.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 CHANGED
@@ -25,7 +25,7 @@ An elmish component basically consists of the following parts:
25
25
  First import everything from `react-elmish` and declare the **Message** discriminated union type:
26
26
 
27
27
  ```ts
28
- import { Cmd, createCmd, UpdateReturnType, UpdateMap } from "react-elmish";
28
+ import { Cmd, createCmd, InitResult, UpdateReturnType, UpdateMap } from "react-elmish";
29
29
 
30
30
  export type Message =
31
31
  | { name: "increment" }
@@ -57,21 +57,14 @@ export interface Props {
57
57
  }
58
58
  ```
59
59
 
60
- Now we create a `cmd` object for our messages type:
61
-
62
- ```ts
63
- const cmd = createCmd<Message>();
64
- ```
65
-
66
60
  To create the initial model we need an **init** function:
67
61
 
68
62
  ```ts
69
- export function init (props: Props): [Model, Cmd<Message>] {
63
+ export function init (props: Props): InitResult {
70
64
  return [
71
65
  {
72
66
  value: props.initialValue,
73
- },
74
- cmd.none
67
+ }
75
68
  ];
76
69
  };
77
70
  ```
@@ -251,75 +244,19 @@ You can also use **Symbols** for the message type instead of strings:
251
244
  ...
252
245
  ```
253
246
 
254
- ## Setup
247
+ ## Dispatch commands in the update map or update function
255
248
 
256
- **react-elmish** works without a setup. But if you want to use logging or some middleware, you can setup **react-elmish** at the start of your program.
249
+ In addition to modifying the model, you can dispatch new commands here.
257
250
 
258
- ```ts
259
- import * as Elm from "react-elmish";
251
+ To do so, you have to create a `cmd` object:
260
252
 
261
- const myLogger = {
262
- debug(...args: unknown []) {
263
- console.debug(...args);
264
- },
265
- info(...args: unknown []) {
266
- console.info(...args);
267
- },
268
- error(...args: unknown []) {
269
- console.error(...args);
270
- },
271
- }
253
+ ```ts
254
+ import { createCmd } from "react-elmish";
272
255
 
273
- Elm.init({
274
- logger: myLogger,
275
- errorMiddleware: error => Toast.error(error.message),
276
- dispatchMiddleware: msg => console.log(msg),
277
- });
256
+ const cmd = createCmd<Message>();
278
257
  ```
279
258
 
280
- The error middleware function is called by the `handleError` function (see [Error handling](#error-handling)).
281
-
282
- The dispatch middleware function is called whenever a Message is dispatched.
283
-
284
- ## Error handling
285
-
286
- You can handle errors easily with the following pattern.
287
-
288
- 1. Add an error message:
289
-
290
- ```ts
291
- import { ErrorMessage, errorMsg, handleError } from "react-elmish";
292
-
293
- export type Message =
294
- | ...
295
- | ErrorMessage;
296
- ```
297
-
298
- 1. Optionally add the convenient function to the **Msg** object:
299
-
300
- ```ts
301
- export const Msg = {
302
- ...
303
- ...errorMsg,
304
- }
305
- ```
306
-
307
- 1. Handle the error message in the **update** function:
308
-
309
- ```ts
310
- ...
311
- case "error":
312
- return handleError(msg.error);
313
- ...
314
- ```
315
-
316
- The **handleError** function then calls your error handling middleware.
317
-
318
- ## Dispatch commands in the update map or update function
319
-
320
- In addition to modifying the model, you can dispatch new commands here.
321
-
322
- To do so, you can call one of the functions in the `cmd` object:
259
+ Then you can call one of the functions of that object:
323
260
 
324
261
  | Function | Description |
325
262
  |---|---|
@@ -332,6 +269,7 @@ To do so, you can call one of the functions in the `cmd` object:
332
269
  | `cmd.ofPromise.either` | Calls an async function and maps the result into a message. |
333
270
  | `cmd.ofPromise.attempt` | Like `either` but ignores the success case. |
334
271
  | `cmd.ofPromise.perform` | Like `either` but ignores the error case. |
272
+ | `cmd.ofSub` | Use this function to trigger a command in a subscription. |
335
273
 
336
274
  ### Dispatch a message
337
275
 
@@ -348,6 +286,8 @@ export const Msg = {
348
286
  printLastMessage: (message: string): Message => ({ name: "printLastMessage", message }),
349
287
  ...
350
288
  }
289
+
290
+ const cmd = createCmd<Message>();
351
291
  ```
352
292
 
353
293
  In the **update** function you can dispatch that message like this:
@@ -408,6 +348,191 @@ case "error":
408
348
  ...
409
349
  ```
410
350
 
351
+ ### Dispatch a command from `init`
352
+
353
+ The same way as in the `update` map or function, you can also dispatch an initial command in the `init` function:
354
+
355
+ ```ts
356
+ export function init (props: Props): InitResult {
357
+ return [
358
+ {
359
+ value: props.initialValue,
360
+ },
361
+ cmd.ofMsg(Msg.loadData())
362
+ ];
363
+ };
364
+ ```
365
+
366
+ ## Subscriptions
367
+
368
+ ### Working with external sources of events
369
+
370
+ If you want to use external sources of events (e.g. a timer), you can use a `subscription`. With this those events can be processed by our `update` handler.
371
+
372
+ Let's define a `Model` and a `Message`:
373
+
374
+ ```ts
375
+ type Message =
376
+ | { name: "timer", date: Date };
377
+
378
+ interface Model {
379
+ date: Date,
380
+ }
381
+
382
+ const Msg = {
383
+ timer: (date: Date): Message => ({ name: "timer", date }),
384
+ };
385
+ ```
386
+
387
+ Now we define the `init` function and the `update` object:
388
+
389
+ ```ts
390
+ const cmd = createCmd<Message>();
391
+
392
+ function init (props: Props): InitResult<Model, Message> {
393
+ return [{
394
+ date: new Date(),
395
+ }];
396
+ }
397
+
398
+ const update: UpdateMap<Props, Model, Message> = {
399
+ timer ({ date }) {
400
+ return [{ date }];
401
+ },
402
+ };
403
+ ```
404
+
405
+ Then we write our `subscription` function:
406
+
407
+ ```ts
408
+ function subscription (model: Model): SubscriptionResult<Message> {
409
+ const sub = (dispatch: Dispatch<Message>): void => {
410
+ setInterval(() => dispatch(Msg.timer(new Date())), 1000) as unknown as number;
411
+ }
412
+
413
+ return [cmd.ofSub(sub)];
414
+ }
415
+ ```
416
+
417
+ This function gets the initialized model as parameter and returns a command.
418
+
419
+ In the function component we call `useElmish` and pass the subscription to it:
420
+
421
+ ```ts
422
+ const [{ date }] = useElmish({ name: "Subscriptions", props, init, update, subscription })
423
+ ```
424
+
425
+ You can define and aggregate multiple subscriptions with a call to `cmd.batch(...)`.
426
+
427
+ ### Cleanup subscriptions
428
+
429
+ In the solution above `setInterval` will trigger events even if the component is removed from the DOM. To cleanup subscriptions, we can return a `destructor` function from the subscription the same as in the `useEffect` hook.
430
+
431
+ Let's rewrite our `subscription` function:
432
+
433
+ ```ts
434
+ function subscription (model: Model): SubscriptionResult<Message> {
435
+ let timer: NodeJS.Timer;
436
+
437
+ const sub = (dispatch: Dispatch<Message>): void => {
438
+ timer = setInterval(() => dispatch(Msg.timer(new Date())), 1000);
439
+ }
440
+
441
+ const destructor = () => {
442
+ clearInterval(timer1);
443
+ }
444
+
445
+ return [cmd.ofSub(sub), destructor];
446
+ }
447
+ ```
448
+
449
+ Here we save the return value of `setInterval` and clear that interval in the returned `destructor` function.
450
+
451
+ ## Setup
452
+
453
+ **react-elmish** works without a setup. But if you want to use logging or some middleware, you can setup **react-elmish** at the start of your program.
454
+
455
+ ```ts
456
+ import { init } from "react-elmish";
457
+
458
+ const myLogger = {
459
+ debug(...args: unknown []) {
460
+ console.debug(...args);
461
+ },
462
+ info(...args: unknown []) {
463
+ console.info(...args);
464
+ },
465
+ error(...args: unknown []) {
466
+ console.error(...args);
467
+ },
468
+ }
469
+
470
+ init({
471
+ logger: myLogger,
472
+ errorMiddleware: error => Toast.error(error.message),
473
+ dispatchMiddleware: msg => myLogger.debug(msg),
474
+ });
475
+ ```
476
+
477
+ The error middleware function is called by the `handleError` function (see [Error handling](#error-handling)).
478
+
479
+ The dispatch middleware function is called whenever a Message is dispatched.
480
+
481
+ ## Error handling
482
+
483
+ You can handle errors easily with the following pattern.
484
+
485
+ 1. Add an error message:
486
+
487
+ ```ts
488
+ import { ErrorMessage, errorHandler, errorMsg, handleError } from "react-elmish";
489
+
490
+ export type Message =
491
+ // | ...
492
+ | ErrorMessage;
493
+ ```
494
+
495
+ 1. Optionally add the convenient function to the `Msg` object:
496
+
497
+ ```ts
498
+ export const Msg = {
499
+ // ...
500
+ ...errorMsg,
501
+ }
502
+ ```
503
+
504
+ 1. Handle the error message
505
+ 1. In the `update` function:
506
+
507
+ ```ts
508
+ // ...
509
+ case "error":
510
+ return handleError(msg.error);
511
+ // ...
512
+ ```
513
+
514
+ 1. Or in the `UpdateMap`:
515
+
516
+ ```ts
517
+ const updateMap = {
518
+ // ...
519
+ error ({ error }) {
520
+ return handleError(error);
521
+ }
522
+ };
523
+ ```
524
+
525
+ Your can also use the `errorHandler` helper function:
526
+
527
+ ```ts
528
+ const updateMap = {
529
+ // ...
530
+ ...errorHandler()
531
+ };
532
+ ```
533
+
534
+ The **handleError** function then calls your error handling middleware.
535
+
411
536
  ## React life cycle management
412
537
 
413
538
  If you want to use `componentDidMount` or `componentWillUnmount` in a class component, don't forget to call the base class implementation of it as the **ElmComponent** is using them internally.
package/dist/Cmd.d.ts CHANGED
@@ -9,25 +9,29 @@ declare type Sub<TMsg> = (dispatch: Dispatch<TMsg>, fallback?: FallbackHandler)
9
9
  */
10
10
  export declare type Cmd<TMsg> = Sub<TMsg>[];
11
11
  /**
12
- * Class to create commands.
13
- * @class Command
14
- * @template TMsg Type of the Msg discriminated union.
12
+ * Contains functions to create commands.
13
+ * @template TMsg Type of the Message discriminated union.
15
14
  */
16
- declare class Command<TMsg> {
15
+ interface Command<TMsg> {
17
16
  /**
18
17
  * Represents an empty command.
19
18
  */
20
- none: never[];
19
+ none: [];
21
20
  /**
22
21
  * Creates a command out of a specific message.
23
22
  * @param {TMsg} msg The specific message.
24
23
  */
25
- ofMsg(msg: TMsg): Cmd<TMsg>;
24
+ ofMsg: (msg: TMsg) => Cmd<TMsg>;
26
25
  /**
27
26
  * Aggregates multiple commands.
28
27
  * @param {Cmd<TMsg> []} commands Array of commands.
29
28
  */
30
- batch(...commands: Cmd<TMsg>[]): Cmd<TMsg>;
29
+ batch: (...commands: Cmd<TMsg>[]) => Cmd<TMsg>;
30
+ /**
31
+ * Command to call the subscriber.
32
+ * @param {Sub<TMsg>} sub The subscriber function.
33
+ */
34
+ ofSub: (sub: Sub<TMsg>) => Cmd<TMsg>;
31
35
  /**
32
36
  * Provides functionalities to create commands from simple functions.
33
37
  */
@@ -39,21 +43,21 @@ declare class Command<TMsg> {
39
43
  * @param ofError Creates the message to dispatch when an error occurred.
40
44
  * @param args The parameters of the task.
41
45
  */
42
- either<TArgs extends unknown[], TReturn>(task: (...args: TArgs) => TReturn, ofSuccess: (result: TReturn) => TMsg, ofError: (error: Error) => TMsg, ...args: TArgs): Cmd<TMsg>;
46
+ either: <TArgs extends unknown[], TReturn>(task: (...args: TArgs) => TReturn, ofSuccess: (result: TReturn) => TMsg, ofError: (error: Error) => TMsg, ...args: TArgs) => Cmd<TMsg>;
43
47
  /**
44
48
  * Creates a command out of a simple function and ignores the error case.
45
49
  * @param task The function to call.
46
50
  * @param ofSuccess Creates the message to dispatch after a successful call of the task.
47
51
  * @param args The parameters of the task.
48
52
  */
49
- perform<TArgs_1 extends unknown[], TReturn_1>(task: (...args: TArgs_1) => TReturn_1, ofSuccess: (result: TReturn_1) => TMsg, ...args: TArgs_1): Cmd<TMsg>;
53
+ perform: <TArgs extends unknown[], TReturn>(task: (...args: TArgs) => TReturn, ofSuccess: (result: TReturn) => TMsg, ...args: TArgs) => Cmd<TMsg>;
50
54
  /**
51
55
  * Creates a command out of a simple function and ignores the success case.
52
56
  * @param task The function to call.
53
57
  * @param ofError Creates the message to dispatch when an error occurred.
54
58
  * @param args The parameters of the task.
55
59
  */
56
- attempt<TArgs_2 extends unknown[], TReturn_2>(task: (...args: TArgs_2) => TReturn_2, ofError: (error: Error) => TMsg, ...args: TArgs_2): Cmd<TMsg>;
60
+ attempt: <TArgs extends unknown[], TReturn>(task: (...args: TArgs) => TReturn, ofError: (error: Error) => TMsg, ...args: TArgs) => Cmd<TMsg>;
57
61
  };
58
62
  /**
59
63
  * Provides functionalities to create commands from async functions.
@@ -66,27 +70,26 @@ declare class Command<TMsg> {
66
70
  * @param ofError Creates the message to dispatch when the promise is rejected.
67
71
  * @param args The parameters of the task.
68
72
  */
69
- either<TArgs extends unknown[], TReturn>(task: (...args: TArgs) => Promise<TReturn>, ofSuccess: (result: TReturn) => TMsg, ofError: (error: Error) => TMsg, ...args: TArgs): Cmd<TMsg>;
73
+ either: <TArgs extends unknown[], TReturn>(task: (...args: TArgs) => Promise<TReturn>, ofSuccess: (result: TReturn) => TMsg, ofError: (error: Error) => TMsg, ...args: TArgs) => Cmd<TMsg>;
70
74
  /**
71
75
  * Creates a command out of an async function and ignores the error case.
72
76
  * @param task The async function to call.
73
77
  * @param ofSuccess Creates the message to dispatch when the promise is resolved.
74
78
  * @param args The parameters of the task.
75
79
  */
76
- perform<TArgs_1 extends unknown[], TReturn_1>(task: (...args: TArgs_1) => Promise<TReturn_1>, ofSuccess: (result: TReturn_1) => TMsg, ...args: TArgs_1): Cmd<TMsg>;
80
+ perform: <TArgs extends unknown[], TReturn>(task: (...args: TArgs) => Promise<TReturn>, ofSuccess: (result: TReturn) => TMsg, ...args: TArgs) => Cmd<TMsg>;
77
81
  /**
78
82
  * Creates a command out of an async function and ignores the success case.
79
83
  * @param task The async function to call.
80
84
  * @param ofError Creates the message to dispatch when the promise is rejected.
81
85
  * @param args The parameters of the task.
82
86
  */
83
- attempt<TArgs_2 extends unknown[], TReturn_2>(task: (...args: TArgs_2) => Promise<TReturn_2>, ofError: (error: Error) => TMsg, ...args: TArgs_2): Cmd<TMsg>;
87
+ attempt: <TArgs extends unknown[], TReturn>(task: (...args: TArgs) => Promise<TReturn>, ofError: (error: Error) => TMsg, ...args: TArgs) => Cmd<TMsg>;
84
88
  };
85
89
  }
86
90
  /**
87
91
  * Creates a typed instance of the Command class.
88
92
  * @template TMsg The type of the Msg discriminated union.
89
- * @see Command
90
93
  */
91
94
  export declare function createCmd<TMsg>(): Command<TMsg>;
92
95
  export {};