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 +198 -73
- package/dist/Cmd.d.ts +17 -14
- package/dist/Cmd.js +42 -123
- package/dist/ElmComponent.d.ts +2 -7
- package/dist/ElmComponent.js +4 -4
- package/dist/ErrorHandling.d.ts +33 -0
- package/dist/ErrorHandling.js +56 -0
- package/dist/Testing/index.d.ts +1 -2
- package/dist/Testing/index.js +1 -1
- package/dist/{ElmUtilities.d.ts → Types.d.ts} +8 -15
- package/dist/Types.js +6 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +17 -5
- package/dist/legacy/useElmish.d.ts +3 -4
- package/dist/legacy/useElmish.js +5 -2
- package/dist/legacy/useElmishMap.d.ts +3 -3
- package/dist/legacy/useElmishMap.js +5 -2
- package/dist/useElmish.d.ts +7 -5
- package/dist/useElmish.js +22 -4
- package/package.json +2 -2
- package/dist/ElmUtilities.js +0 -35
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):
|
|
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
|
-
##
|
|
247
|
+
## Dispatch commands in the update map or update function
|
|
255
248
|
|
|
256
|
-
|
|
249
|
+
In addition to modifying the model, you can dispatch new commands here.
|
|
257
250
|
|
|
258
|
-
|
|
259
|
-
import * as Elm from "react-elmish";
|
|
251
|
+
To do so, you have to create a `cmd` object:
|
|
260
252
|
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
13
|
-
* @
|
|
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
|
-
|
|
15
|
+
interface Command<TMsg> {
|
|
17
16
|
/**
|
|
18
17
|
* Represents an empty command.
|
|
19
18
|
*/
|
|
20
|
-
none:
|
|
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)
|
|
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>[])
|
|
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)
|
|
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<
|
|
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<
|
|
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)
|
|
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<
|
|
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<
|
|
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 {};
|