react-elmish 2.2.0 → 3.0.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,66 +25,82 @@ 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 * as Elm from "react-elmish";
28
+ import { Cmd, createCmd, UpdateReturnType, UpdateMap } from "react-elmish";
29
29
 
30
30
  export type Message =
31
- | { name: "Increment" }
32
- | { name: "Decrement" }
33
- ;
31
+ | { name: "increment" }
32
+ | { name: "decrement" };
34
33
  ```
35
34
 
36
- You can also create some convenience functions to dispatch a message:
35
+ You can also create some convenience functions to create message objects:
37
36
 
38
37
  ```ts
39
38
  export const Msg = {
40
- increment: (): Message => ({ name: "Increment" }),
41
- decrement: (): Message => ({ name: "Decrement" }),
39
+ increment: (): Message => ({ name: "increment" }),
40
+ decrement: (): Message => ({ name: "decrement" }),
42
41
  };
43
42
  ```
44
43
 
45
- Now we can create a `cmd` object for our messages type:
46
-
47
- ```ts
48
- const cmd = Elm.createCmd<Message>();
49
- ```
50
-
51
44
  Next, declare the model:
52
45
 
53
46
  ```ts
54
- export type Model = Readonly<{
47
+ export interface Model {
55
48
  value: number,
56
- }>;
49
+ }
57
50
  ```
58
51
 
59
52
  The props are optional:
60
53
 
61
54
  ```ts
62
- export type Props = {
55
+ export interface Props {
63
56
  initialValue: number,
64
- };
57
+ }
58
+ ```
59
+
60
+ Now we create a `cmd` object for our messages type:
61
+
62
+ ```ts
63
+ const cmd = createCmd<Message>();
65
64
  ```
66
65
 
67
66
  To create the initial model we need an **init** function:
68
67
 
69
68
  ```ts
70
- export const init = (props: Props): [Model, Elm.Cmd<Message>] => {
71
- const model: Model = {
72
- value: props.initialValue,
73
- };
69
+ export function init (props: Props): [Model, Cmd<Message>] {
70
+ return [
71
+ {
72
+ value: props.initialValue,
73
+ },
74
+ cmd.none
75
+ ];
76
+ };
77
+ ```
74
78
 
75
- return [model, cmd.none];
79
+ To update the model based on a message we need an `UpdateMap` object:
80
+
81
+ ```ts
82
+ export const update: UpdateMap<Props, Model, Message> = {
83
+ increment (msg, model, props) {
84
+ return [{ value: model.value + 1 }];
85
+ },
86
+
87
+ decrement (msg, model, props) {
88
+ return [{ value: model.value - 1 }];
89
+ },
76
90
  };
77
91
  ```
78
92
 
79
- To update the model based on a message we need an **update** function:
93
+ **Note:** When using an `UpdateMap` it is recommended to use camelCase for message names ("increment" instead of "Increment").
94
+
95
+ Alternatively we can use an **update** function:
80
96
 
81
97
  ```ts
82
- export const update = (model: Model, msg: Msg, props: Props): Elm.UpdateReturnType<Model, Message> => {
98
+ export const update = (model: Model, msg: Msg, props: Props): UpdateReturnType<Model, Message> => {
83
99
  switch (msg.name) {
84
- case "Increment":
100
+ case "increment":
85
101
  return [{ value: model.value + 1 }];
86
102
 
87
- case "Decrement":
103
+ case "decrement":
88
104
  return [{ value: model.value - 1 }];
89
105
  }
90
106
  };
@@ -96,25 +112,49 @@ export const update = (model: Model, msg: Msg, props: Props): Elm.UpdateReturnTy
96
112
 
97
113
  To put all this together and to render our component, we need a React component.
98
114
 
99
- This can be a **class component**:
115
+ As a **function component**:
116
+
117
+ ```tsx
118
+ // Import everything from the App.ts
119
+ import { init, update, Msg, Props } from "../App";
120
+ // Import the useElmish hook
121
+ import { useElmish } from "react-elmish";
122
+
123
+ function App (props: Props): JSX.Element {
124
+ // Call the useElmish hook, it returns the current model and the dispatch function
125
+ const [model, dispatch] = useElmish({ props, init, update, name: "App" });
126
+
127
+ return (
128
+ <div>
129
+ {/* Display our current value */}
130
+ <p>{model.value}</p>
131
+
132
+ {/* dispatch messages */}
133
+ <button onClick={() => dispatch(Msg.increment())}>Increment</button>
134
+ <button onClick={() => dispatch(Msg.decrement())}>Decrement</button>
135
+ </div>
136
+ );
137
+ }
138
+ ```
139
+
140
+ As a **class component**:
100
141
 
101
142
  ```tsx
102
143
  // Import everything from the App.ts
103
- import * as Shared from "../App";
144
+ import { Model, Message, Props, init, update, Msg } as Shared from "../App";
104
145
  // Import the ElmComponent which extends the React.Component
105
146
  import { ElmComponent } from "react-elmish";
106
- // Don't forget to import react
107
147
  import React from "react";
108
148
 
109
149
  // Create an elmish class component
110
- class App extends ElmComponent<Shared.Model, Shared.Message, Shared.Props> {
150
+ class App extends ElmComponent<Model, Message, Props> {
111
151
  // Construct the component with the props and init function
112
- constructor(props: Shared.Props) {
113
- super(props, Shared.init, "App");
152
+ constructor(props: Props) {
153
+ super(props, init, "App");
114
154
  }
115
155
 
116
156
  // Assign our update function to the component
117
- update = Shared.update;
157
+ update = update;
118
158
 
119
159
  render(): React.ReactNode {
120
160
  // Access the model
@@ -126,72 +166,19 @@ class App extends ElmComponent<Shared.Model, Shared.Message, Shared.Props> {
126
166
  <p>{value}</p>
127
167
 
128
168
  {/* Dispatch messages */}
129
- <button onClick={() => this.dispatch(Shared.Msg.increment())}>Increment</button>
130
- <button onClick={() => this.dispatch(Shared.Msg.decrement())}>Decrement</button>
169
+ <button onClick={() => this.dispatch(Msg.increment())}>Increment</button>
170
+ <button onClick={() => this.dispatch(Msg.decrement())}>Decrement</button>
131
171
  </div>
132
172
  );
133
173
  }
134
174
  ```
135
175
 
136
- Or it can be a **functional component**:
137
-
138
- ```tsx
139
- // Import everything from the App.ts
140
- import * as Shared from "../App";
141
- // Import the useElmish hook
142
- import { useElmish } from "react-elmish";
143
-
144
- const App = (props: Shared.Props) => {
145
- // Call the useElmish hook, it returns the current model and the dispatch function
146
- const [model, dispatch] = useElmish(props, Shared.init, Shared.update, "App");
147
-
148
- return (
149
- <div>
150
- {/* Display our current value */}
151
- <p>{model.value}</p>
152
-
153
- {/* dispatch messages */}
154
- <button onClick={() => dispatch(Shared.Msg.increment())}>Increment</button>
155
- <button onClick={() => dispatch(Shared.Msg.decrement())}>Decrement</button>
156
- </div>
157
- );
158
- };
159
- ```
160
-
161
176
  You can use these components like any other React component.
162
177
 
163
178
  > **Note**: It is recommended to separate business logic and the view into separate modules. Here we put the `Messages`, `Model`, `Props`, `init`, and `update` functions into **App.ts**. The elmish React Component resides in a **Components** subfolder and is named **App.tsx**.
164
179
  >
165
180
  > You can even split the contents of the **App.ts** into two files: **Types.ts** (`Message`, `Model`, and `Props`) and **State.ts** (`init` and `update`).
166
181
 
167
- ## A new approach
168
-
169
- Instead of `useElmish` you can use the `useElmishMap` hook. Then you have an `UpdateMap` instead of an `update` function:
170
-
171
- ```ts
172
- const updateMap: UpdateMap<Props, Model, Message> {
173
- // Now the message is the first parameter, so it is easier to omit the model parameter.
174
- Increment: (msg) => [{ value: model.value + 1 }],
175
- Decrement: (msg) => [{ value: model.value - 1 }],
176
- }
177
- ```
178
-
179
- Add your component looks like:
180
-
181
- ```tsx
182
- import { useElmishMap } from "react-elmish";
183
-
184
- const App = (props: Shared.Props) => {
185
- const [model, dispatch] = useElmishMap(props, init, updateMap, "App");
186
-
187
- return (
188
- <div>
189
- ...
190
- </div>
191
- );
192
- };
193
- ```
194
-
195
182
  ## More on messages
196
183
 
197
184
  ### Message arguments
@@ -200,11 +187,11 @@ Messages can also have arguments. You can modify the example above and pass an o
200
187
 
201
188
  ```ts
202
189
  export type Message =
203
- | { name: "Increment", step?: number }
190
+ | { name: "increment", step?: number }
204
191
  ...
205
192
 
206
193
  export const Msg = {
207
- increment: (step?: number): Message => ({ name: "Increment", step }),
194
+ increment: (step?: number): Message => ({ name: "increment", step }),
208
195
  ...
209
196
  }
210
197
  ```
@@ -213,7 +200,7 @@ Then use this argument in the **update** function:
213
200
 
214
201
  ```ts
215
202
  ...
216
- case "Increment":
203
+ case "increment":
217
204
  return [{ value: model.value + (msg.step ?? 1)}]
218
205
  ...
219
206
  ```
@@ -301,10 +288,11 @@ You can handle errors easily with the following pattern.
301
288
  1. Add an error message:
302
289
 
303
290
  ```ts
291
+ import { ErrorMessage, errorMsg, handleError } from "react-elmish";
292
+
304
293
  export type Message =
305
294
  | ...
306
- | { name: "Error", error: Error }
307
- ;
295
+ | ErrorMessage;
308
296
  ```
309
297
 
310
298
  1. Optionally add the convenient function to the **Msg** object:
@@ -312,7 +300,7 @@ You can handle errors easily with the following pattern.
312
300
  ```ts
313
301
  export const Msg = {
314
302
  ...
315
- error: (error: Error): Message => ({ name: "Error", error }),
303
+ ...errorMsg,
316
304
  }
317
305
  ```
318
306
 
@@ -320,16 +308,16 @@ You can handle errors easily with the following pattern.
320
308
 
321
309
  ```ts
322
310
  ...
323
- case "Error":
324
- return Elm.handleError(msg.error);
311
+ case "error":
312
+ return handleError(msg.error);
325
313
  ...
326
314
  ```
327
315
 
328
316
  The **handleError** function then calls your error handling middleware.
329
317
 
330
- ## Dispatch commands in the update function
318
+ ## Dispatch commands in the update map or update function
331
319
 
332
- In addition to modifying the model, you can dispatch new commands in the **update** function.
320
+ In addition to modifying the model, you can dispatch new commands here.
333
321
 
334
322
  To do so, you can call one of the functions in the `cmd` object:
335
323
 
@@ -352,12 +340,12 @@ Let's assume you have a message to display the description of the last called me
352
340
  ```ts
353
341
  export type Message =
354
342
  ...
355
- | { name: "PrintLastMessage", message: string }
343
+ | { name: "printLastMessage", message: string }
356
344
  ...
357
345
 
358
346
  export const Msg = {
359
347
  ...
360
- printLastMessage: (message: string): Message => ({ name: "PrintLastMessage", message }),
348
+ printLastMessage: (message: string): Message => ({ name: "printLastMessage", message }),
361
349
  ...
362
350
  }
363
351
  ```
@@ -365,11 +353,11 @@ export const Msg = {
365
353
  In the **update** function you can dispatch that message like this:
366
354
 
367
355
  ```ts
368
- case "Increment":
356
+ case "increment":
369
357
  return [{ value: model.value + 1 }, cmd.ofMsg(Msg.printLastMessage("Incremented by one"))];
370
358
  ```
371
359
 
372
- This new message will immediately be dispatched after returning from the **update** function.
360
+ This new message will immediately be dispatched after returning from the **update**.
373
361
 
374
362
  ### Call an async function
375
363
 
@@ -387,16 +375,16 @@ you can define the following messages:
387
375
  ```ts
388
376
  export type Messages =
389
377
  ...
390
- | { name: "LoadSettings" },
391
- | { name: "SettingsLoaded", settings: Settings }
392
- | { name: "Error", error: Error }
378
+ | { name: "loadSettings" },
379
+ | { name: "settingsLoaded", settings: Settings }
380
+ | ErrorMessage
393
381
  ...
394
382
 
395
383
  export const Msg = {
396
384
  ...
397
- loadSettings: (): Message => ({ name: "LoadSettings" }),
398
- settingsLoaded: (settings: Settings): Message => ({ name: "SettingsLoaded", settings }),
399
- error: (error: Error): Message => ({ name: "Error", error }),
385
+ loadSettings: (): Message => ({ name: "loadSettings" }),
386
+ settingsLoaded: (settings: Settings): Message => ({ name: "settingsLoaded", settings }),
387
+ ...errorMsg,
400
388
  ...
401
389
  };
402
390
  ```
@@ -405,18 +393,18 @@ and handle the messages in the **update** function:
405
393
 
406
394
  ```ts
407
395
  ...
408
- case "LoadSettings":
396
+ case "loadSettings":
409
397
  // Create a command out of the async function with the provided arguments
410
398
  // If loadSettings resolves it dispatches "SettingsLoaded"
411
399
  // If it fails it dispatches "Error"
412
400
  // The return type of loadSettings must fit Msg.settingsLoaded
413
401
  return [{}, cmd.ofPromise.either(loadSettings, Msg.settingsLoaded, Msg.error, "firstArg", 123)];
414
402
 
415
- case "SettingsLoaded":
403
+ case "settingsLoaded":
416
404
  return [{ settings: msg.settings }];
417
405
 
418
- case "Error":
419
- return Elm.handleError(msg.error);
406
+ case "error":
407
+ return handleError(msg.error);
420
408
  ...
421
409
  ```
422
410
 
@@ -448,34 +436,141 @@ In a functional component you can use the **useEffect** hook as normal.
448
436
 
449
437
  If you have some business logic that you want to reuse in other components, you can do this by using different sources for messages.
450
438
 
439
+ ### With an `UpdateMap`
440
+
451
441
  Let's say you want to load some settings, you can write a module like this:
452
442
 
453
443
  ```ts LoadSettings.ts
454
- import * as Elm from "react-elmish";
444
+ import { createCmd, Cmd, ErrorMessage, UpdateMap, handleError } from "react-elmish";
445
+
446
+ export interface Settings {
447
+ // ...
448
+ }
449
+
450
+ export type Message =
451
+ | { name: "loadSettings" }
452
+ | { name: "settingsLoaded", settings: Settings }
453
+ | ErrorMessage;
454
+
455
+ export const Msg = {
456
+ loadSettings: (): Message => ({ name: "loadSettings" }),
457
+ settingsLoaded: (settings: Settings): Message => ({ name: "settingsLoaded", settings }),
458
+ error: (error: Error): Message => ({ name: "error", error }),
459
+ };
460
+
461
+ const cmd = createCmd<Message>();
462
+
463
+ export interface Model {
464
+ settings: Settings | null,
465
+ }
466
+
467
+ export function init (): Model {
468
+ return {
469
+ settings: null
470
+ };
471
+ }
472
+
473
+ export const update: UpdateMap<Props, Model, Message> = {
474
+ loadSettings () {
475
+ return [{}, cmd.ofPromise.either(loadSettings, Msg.settingsLoaded, Msg.error)];
476
+ }
477
+
478
+ settingsLoaded ({ settings }) {
479
+ return [{ settings }];
480
+ }
481
+
482
+ error ({ error }) {
483
+ return handleError(error);
484
+ }
485
+ };
486
+
487
+ async function loadSettings (): Promise<Settings> {
488
+ // Call some service (e.g. database or backend)
489
+ return {};
490
+ }
491
+ ```
492
+
493
+ > **Note**: This module has no **View**.
494
+
495
+ Now let's integrate the **LoadSettings** module in our component:
496
+
497
+ ```ts Composition.ts
498
+ // Import the LoadSettings module
499
+ import * as LoadSettings from "./LoadSettings";
500
+ import { createCmd, Cmd, } from "react-elmish";
501
+
502
+ // Here we define our local messages
503
+ type Message =
504
+ | { name: "myMessage" }
505
+ | LoadSettings.Message;
506
+
507
+ // And spread the Msg of LoadSettings object
508
+ export const Msg = {
509
+ myMessage: (): Message => ({ name: "myMessage" }),
510
+ ...LoadSettings.Msg,
511
+ };
512
+
513
+ const cmd = Elm.createCmd<Message>();
514
+
515
+ interface Props {}
516
+
517
+ // Extend the LoadSettings model
518
+ interface Model extends LoadSettings.Model {
519
+ // ...
520
+ }
521
+
522
+ export const init = (): [Model, Cmd<Message>] => {
523
+ // Return the model and dispatch the LoadSettings message
524
+ return [
525
+ {
526
+ // Spread the initial model from LoadSettings
527
+ ...LoadSettings.init(),
528
+ // ...
529
+ },
530
+ cmd.ofMsg(Msg.loadSettings())
531
+ ];
532
+ };
533
+
534
+ // Spread the UpdateMap of LoadSettings into our update map
535
+ const update: UpdateMap<Props, Model, Message> = {
536
+ myMessage () {
537
+ return [{}];
538
+ },
539
+
540
+ ...LoadSettings.update,
541
+ };
542
+ ```
543
+
544
+ ## With an update function
545
+
546
+ Let's say you want to load some settings, you can write a module like this:
547
+
548
+ ```ts LoadSettings.ts
549
+ import { MsgSource, ErrorMessage, createCmd, UpdateReturnType, handleError } from "react-elmish";
455
550
 
456
551
  export type Settings = {
457
552
  // ...
458
553
  };
459
554
 
460
555
  // We use a MsgSource to differentiate between the messages
461
- type MessageSource = Elm.MsgSource<"LoadSettings">;
556
+ type MessageSource = MsgSource<"LoadSettings">;
462
557
 
463
558
  // Add that MessageSource to all the messages
464
559
  export type Message =
465
- | { name: "LoadSettings" } & MessageSource
466
- | { name: "SettingsLoaded", settings: Settings } & MessageSource
467
- | { name: "Error", error: Error } & MessageSource
560
+ | { name: "loadSettings" } & MessageSource
561
+ | { name: "settingsLoaded", settings: Settings } & MessageSource
562
+ | ErrorMessage & MessageSource
468
563
 
469
564
  // Do the same for the convenient functions
470
565
  const MsgSource: MessageSource = { source: "LoadSettings" };
471
566
 
472
567
  export const Msg = {
473
- loadSettings: (): Message => ({ name: "LoadSettings", ...MsgSource }),
474
- settingsLoaded: (settings: Settings): Message => ({ name: "SettingsLoaded", settings, ...MsgSource }),
475
- error: (error: Error): Message => ({ name: "Error", error, ...MsgSource }),
568
+ loadSettings: (): Message => ({ name: "loadSettings", ...MsgSource }),
569
+ settingsLoaded: (settings: Settings): Message => ({ name: "settingsLoaded", settings, ...MsgSource }),
570
+ error: (error: Error): Message => ({ name: "error", error, ...MsgSource }),
476
571
  };
477
572
 
478
- const cmd = Elm.createCmd<Message>();
573
+ const cmd = createCmd<Message>();
479
574
 
480
575
  export type Model = Readonly<{
481
576
  settings: Settings | null,
@@ -485,23 +580,23 @@ export const init = (): Model => ({
485
580
  settings: null,
486
581
  });
487
582
 
488
- export const update = (_model: Model, msg: Message): Elm.UpdateReturnType<Model, Message> => {
583
+ export const update = (_model: Model, msg: Message): UpdateReturnType<Model, Message> => {
489
584
  switch (msg.name) {
490
- case "LoadSettings":
585
+ case "loadSettings":
491
586
  return [{}, cmd.ofPromise.either(loadSettings, Msg.settingsLoaded, Msg.error)];
492
587
 
493
- case "SettingsLoaded":
588
+ case "settingsLoaded":
494
589
  return [{ settings: msg.settings }];
495
590
 
496
- case "Error":
497
- return Elm.handleError(msg.error);
591
+ case "error":
592
+ return handleError(msg.error);
498
593
  }
499
594
  };
500
595
 
501
- const loadSettings = async (): Promise<Settings> => {
596
+ async function loadSettings (): Promise<Settings> {
502
597
  // Call some service (e.g. database or backend)
503
- return Promise.resolve({});
504
- };
598
+ return {};
599
+ }
505
600
  ```
506
601
 
507
602
  > **Note**: This module has no **View**.
@@ -509,51 +604,51 @@ const loadSettings = async (): Promise<Settings> => {
509
604
  In other components where we want to use this **LoadSettings** module, we also need a message source:
510
605
 
511
606
  ```ts Composition.ts
512
- import * as Elm from "react-elmish";
607
+ import { createCmd, MsgSource, UpdateReturnType, Cmd } from "react-elmish";
513
608
  // Import the LoadSettings module
514
609
  import * as LoadSettings from "./LoadSettings";
515
610
 
516
611
  // Create a message source for this module
517
- type MessageSource = Elm.MsgSource<"Composition">;
612
+ type MessageSource = MsgSource<"Composition">;
518
613
 
519
614
  // Here we define our local messages
520
615
  // We don't need to export them
521
616
  type CompositionMessage =
522
- | { name: "MyMessage" } & MessageSource
523
- ;
617
+ | { name: "myMessage" } & MessageSource;
524
618
 
525
619
  // Combine the local messages and the ones from LoadSettings
526
620
  export type Message =
527
621
  | CompositionMessage
528
- | LoadSettings.Message
529
- ;
622
+ | LoadSettings.Message;
530
623
 
531
624
  const MsgSource: MessageSource = { source: "Composition" };
532
625
 
533
626
  export const Msg = {
534
- myMessage: (): Message => ({ name: "MyMessage", ...MsgSource }),
627
+ myMessage: (): Message => ({ name: "myMessage", ...MsgSource }),
628
+ ...LoadSettings.Msg,
535
629
  };
536
630
 
537
- const cmd = Elm.createCmd<Message>();
631
+ const cmd = createCmd<Message>();
538
632
 
539
633
  // Include the LoadSettings Model
540
- export type Model = Readonly<{
634
+ export interface Model extends LoadSettings.Model {
541
635
  // ...
542
- }> & LoadSettings.Model;
543
-
544
- export const init = (): [Model, Elm.Cmd<Message>] => {
545
- const model: Model = {
546
- // Spread the initial model from LoadSettings
547
- ...LoadSettings.init(),
548
- // ...
549
- };
636
+ }
550
637
 
638
+ export const init = (): [Model, Cmd<Message>] => {
551
639
  // Return the model and dispatch the LoadSettings message
552
- return [model, cmd.ofMsg(LoadSettings.Msg.loadSettings())];
640
+ return [
641
+ {
642
+ // Spread the initial model from LoadSettings
643
+ ...LoadSettings.init(),
644
+ // ...
645
+ },
646
+ cmd.ofMsg(Msg.loadSettings())
647
+ ];
553
648
  };
554
649
 
555
650
  // In our update function, we first distinguish between the sources of the messages
556
- export const update = (model: Model, msg: Message): Elm.UpdateReturnType<Model, Message> => {
651
+ export function update (model: Model, msg: Message): UpdateReturnType<Model, Message> {
557
652
  switch (msg.source) {
558
653
  case "Composition":
559
654
  // Then call the update function for the local messages
@@ -568,7 +663,7 @@ export const update = (model: Model, msg: Message): Elm.UpdateReturnType<Model,
568
663
  // For the msg parameter we use the local CompositionMessage type
569
664
  const updateComposition = (model: Model, msg: CompositionMessage): Elm.UpdateReturnType<Model, Message> => {
570
665
  switch (msg.name) {
571
- case "MyMessage":
666
+ case "myMessage":
572
667
  return [{}];
573
668
  }
574
669
  }
@@ -585,12 +680,12 @@ To inform the parent component about some action, let's say to close a dialog fo
585
680
  ```ts Dialog.ts
586
681
  export type Message =
587
682
  ...
588
- | { name: "Close" }
683
+ | { name: "close" }
589
684
  ...
590
685
 
591
686
  export const Msg = {
592
687
  ...
593
- close: (): Message => ({ name: "Close" }),
688
+ close: (): Message => ({ name: "close" }),
594
689
  ...
595
690
  }
596
691
  ```
@@ -607,8 +702,9 @@ To inform the parent component about some action, let's say to close a dialog fo
607
702
 
608
703
  ```ts Dialog.ts
609
704
  ...
610
- case "Close":
705
+ case "close":
611
706
  props.onClose();
707
+
612
708
  return [{}];
613
709
  ...
614
710
  ```
@@ -629,6 +725,7 @@ To test your **update** function you can use some helper functions in `react-elm
629
725
  | --- | --- |
630
726
  | `getOfMsgParams` | Extracts the messages out of a command |
631
727
  | `execCmd` | Executes the provided command and returns an array of all messages. |
728
+ | `getUpdateFn` | returns an `update` function for your update map object. |
632
729
 
633
730
  ### Testing the model and simple message commands
634
731
 
@@ -690,10 +787,10 @@ it("returns the correct cmd", () => {
690
787
 
691
788
  ### Testing with an UpdateMap
692
789
 
693
- To test your update map (when using `useElmishMap`), you can get your `update` function by calling `getUpdateFn`:
790
+ To test your update map, you can get an `update` function by calling `getUpdateFn`:
694
791
 
695
792
  ```ts
696
- import * as Testing from "react-elmish/dist/Testing";
793
+ import { getUpdateFn } from "react-elmish/dist/Testing";
697
794
 
698
795
  const update = getUpdateFn(updateMap);
699
796
 
@@ -701,7 +798,7 @@ const update = getUpdateFn(updateMap);
701
798
  const [model, cmd] = update(msg, model, props);
702
799
  ```
703
800
 
704
- ## Migration from v1.x to 2.x
801
+ ## Migration from v1.x to v2.x
705
802
 
706
803
  * Use `Logger` and `Message` instead of `ILogger` and `IMessage`.
707
804
  * The global declaration of the `Nullable` type was removed, because it is unexpected for this library to declare such a type. You can declare this type for yourself if needed:
@@ -711,3 +808,16 @@ const [model, cmd] = update(msg, model, props);
711
808
  type Nullable<T> = T | null;
712
809
  }
713
810
  ```
811
+
812
+ ## Migration from v2.x to v3.x
813
+
814
+ The signature of `useElmish` has changed. It takes an options object now. Thus there is no need for the `useElmishMap` function. Use the new `useElmish` hook with an `UpdateMap` instead.
815
+
816
+ To use the old `useElmish` and `useElmishMap` functions, import them from the legacy namespace:
817
+
818
+ ```ts
819
+ import { useElmish } from "react-elmish/dist/legacy/useElmish";
820
+ import { useElmishMap } from "react-elmish/dist/legacy/useElmishMap";
821
+ ```
822
+
823
+ **Notice**: These functions are marked as deprecated and will be removed in a later release.