react-elmish 5.1.0 → 5.1.1-beta.1

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
@@ -7,9 +7,8 @@ This library brings the elmish pattern to react.
7
7
 
8
8
  - [Installation](#installation)
9
9
  - [Basic Usage](#basic-usage)
10
- - [More on messages](#more-on-messages)
11
- - [Message arguments](#message-arguments)
12
- - [Symbols instead of strings](#symbols-instead-of-strings)
10
+ - [More about messages](#more-about-messages)
11
+ - [Message parameters](#message-parameters)
13
12
  - [Dispatch commands in the update map or update function](#dispatch-commands-in-the-update-map-or-update-function)
14
13
  - [Dispatch a message](#dispatch-a-message)
15
14
  - [Call an async function](#call-an-async-function)
@@ -25,9 +24,8 @@ This library brings the elmish pattern to react.
25
24
  - [With an update function](#with-an-update-function)
26
25
  - [Call back parent components](#call-back-parent-components)
27
26
  - [Testing](#testing)
28
- - [Testing the model and simple message commands](#testing-the-model-and-simple-message-commands)
27
+ - [Testing the update handler](#testing-the-update-handler)
29
28
  - [Testing all (async) messages](#testing-all-async-messages)
30
- - [Testing with an UpdateMap](#testing-with-an-updatemap)
31
29
  - [UI Tests](#ui-tests)
32
30
  - [Migrations](#migrations)
33
31
  - [From v1.x to v2.x](#from-v1x-to-v2x)
@@ -112,9 +110,9 @@ export const update: UpdateMap<Props, Model, Message> = {
112
110
  };
113
111
  ```
114
112
 
115
- **Note:** When using an `UpdateMap` it is recommended to use camelCase for message names ("increment" instead of "Increment").
113
+ **Note:** When using an `UpdateMap` it is recommended to use camelCase for message names (e.g. "increment" instead of "Increment").
116
114
 
117
- Alternatively we can use an **update** function:
115
+ Alternatively we can use an `update` function:
118
116
 
119
117
  ```ts
120
118
  export const update = (model: Model, msg: Msg, props: Props): UpdateReturnType<Model, Message> => {
@@ -159,12 +157,12 @@ function App (props: Props): JSX.Element {
159
157
  }
160
158
  ```
161
159
 
162
- As a **class component**:
160
+ You can also write the component as a **class component**:
163
161
 
164
162
  ```tsx
165
163
  // Import everything from the App.ts
166
164
  import { Model, Message, Props, init, update, Msg } as Shared from "../App";
167
- // Import the ElmComponent which extends the React.Component
165
+ // Import the ElmComponent which extends React.Component
168
166
  import { ElmComponent } from "react-elmish";
169
167
  import React from "react";
170
168
 
@@ -195,17 +193,19 @@ class App extends ElmComponent<Model, Message, Props> {
195
193
  }
196
194
  ```
197
195
 
196
+ > **Note**: When using a class component, you can only use an `update` function. Class components do not support `UpdateMap`s.
197
+
198
198
  You can use these components like any other React component.
199
199
 
200
200
  > **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**.
201
201
  >
202
202
  > 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`).
203
203
 
204
- ## More on messages
204
+ ## More about messages
205
205
 
206
- ### Message arguments
206
+ ### Message parameters
207
207
 
208
- Messages can also have arguments. You can modify the example above and pass an optional step value to the **Increment** message:
208
+ Messages can also have parameters. You can modify the example above and pass an optional step value to the **Increment** message:
209
209
 
210
210
  ```ts
211
211
  export type Message =
@@ -218,13 +218,17 @@ export const Msg = {
218
218
  }
219
219
  ```
220
220
 
221
- Then use this argument in the **update** function:
221
+ Then use this parameter in the update handler:
222
222
 
223
223
  ```ts
224
- ...
225
- case "increment":
226
- return [{ value: model.value + (msg.step ?? 1)}]
227
- ...
224
+ {
225
+ // ...
226
+ // We destructure the message parameter here
227
+ increment ({ step }) {
228
+ return [{ value: model.value + (step ?? 1)}]
229
+ }
230
+ // ...
231
+ };
228
232
  ```
229
233
 
230
234
  In the **render** method you can add another button to increment the value by 10:
@@ -235,44 +239,6 @@ In the **render** method you can add another button to increment the value by 10
235
239
  ...
236
240
  ```
237
241
 
238
- ### Symbols instead of strings
239
-
240
- You can also use **Symbols** for the message type instead of strings:
241
-
242
- 1. Declare a Symbol for the message:
243
-
244
- ```ts
245
- const ResetMsg = Symbol("reset");
246
- ```
247
-
248
- 1. Use this Symbol as message name:
249
-
250
- ```ts
251
- export type Message =
252
- ...
253
- | { name: typeof ResetMsg }
254
- ...
255
- ```
256
-
257
- 1. Create the convenient function
258
-
259
- ```ts
260
- export const Msg = {
261
- ...
262
- reset: (): Message => ({ name: ResetMsg }),
263
- ...
264
- }
265
- ```
266
-
267
- 1. Handle the new message in the **update** function:
268
-
269
- ```ts
270
- ...
271
- case ResetMsg:
272
- return [{ value: 0 }];
273
- ...
274
- ```
275
-
276
242
  ## Dispatch commands in the update map or update function
277
243
 
278
244
  In addition to modifying the model, you can dispatch new commands here.
@@ -319,14 +285,17 @@ export const Msg = {
319
285
  const cmd = createCmd<Message>();
320
286
  ```
321
287
 
322
- In the **update** function you can dispatch that message like this:
288
+ In the **update map** or **update** function you can dispatch that message like this:
323
289
 
324
290
  ```ts
325
- case "increment":
326
- return [{ value: model.value + 1 }, cmd.ofMsg(Msg.printLastMessage("Incremented by one"))];
291
+ {
292
+ increment () {
293
+ return [{ value: model.value + 1 }, cmd.ofMsg(Msg.printLastMessage("Incremented by one"))];
294
+ }
295
+ }
327
296
  ```
328
297
 
329
- This new message will immediately be dispatched after returning from the **update**.
298
+ This new message will immediately be dispatched after returning from the **update** handler.
330
299
 
331
300
  ### Call an async function
332
301
 
@@ -361,20 +330,25 @@ export const Msg = {
361
330
  and handle the messages in the **update** function:
362
331
 
363
332
  ```ts
364
- ...
365
- case "loadSettings":
366
- // Create a command out of the async function with the provided arguments
367
- // If loadSettings resolves it dispatches "SettingsLoaded"
368
- // If it fails it dispatches "Error"
369
- // The return type of loadSettings must fit Msg.settingsLoaded
370
- return [{}, cmd.ofPromise.either(loadSettings, Msg.settingsLoaded, Msg.error, "firstArg", 123)];
371
-
372
- case "settingsLoaded":
373
- return [{ settings: msg.settings }];
374
-
375
- case "error":
376
- return handleError(msg.error);
377
- ...
333
+ {
334
+ // ...
335
+ loadSettings () {
336
+ // Create a command out of the async function with the provided arguments
337
+ // If loadSettings resolves it dispatches "SettingsLoaded"
338
+ // If it fails it dispatches "Error"
339
+ // The return type of loadSettings must fit Msg.settingsLoaded
340
+ return [{}, cmd.ofPromise.either(loadSettings, Msg.settingsLoaded, Msg.error, "firstArg", 123)];
341
+ },
342
+
343
+ settingsLoaded () {
344
+ return [{ settings: msg.settings }];
345
+ },
346
+
347
+ error () {
348
+ return handleError(msg.error);
349
+ },
350
+ // ...
351
+ };
378
352
  ```
379
353
 
380
354
  ### Dispatch a command from `init`
@@ -855,10 +829,13 @@ To inform the parent component about some action, let's say to close a dialog fo
855
829
  1. Handle the message and call the callback function:
856
830
 
857
831
  ```ts Dialog.ts
858
- ...
859
- case "close":
860
- return [{}, cmd.ofFunc.attempt(props.onClose, Msg.error)];
861
- ...
832
+ {
833
+ // ...
834
+ close () {
835
+ return [{}, cmd.ofFunc.attempt(props.onClose, Msg.error)];
836
+ }
837
+ // ...
838
+ };
862
839
  ```
863
840
 
864
841
  1. In the **render** method of the parent component pass the callback as prop
@@ -871,7 +848,7 @@ To inform the parent component about some action, let's say to close a dialog fo
871
848
 
872
849
  ## Testing
873
850
 
874
- To test your **update** function you can use some helper functions in `react-elmish/dist/Testing`:
851
+ To test your **update** handler you can use some helper functions in `react-elmish/dist/Testing`:
875
852
 
876
853
  | Function | Description |
877
854
  | --- | --- |
@@ -880,7 +857,20 @@ To test your **update** function you can use some helper functions in `react-elm
880
857
  | `getUpdateFn` | Returns an `update` function for your update map object. |
881
858
  | `createUpdateArgsFactory` | Creates a factory function to create a message, a model, and props in a test. |
882
859
 
883
- ### Testing the model and simple message commands
860
+ ### Testing the update handler
861
+
862
+ **Note**: When using an `UpdateMap`, you can get an `update` function by calling `getUpdateFn`:
863
+
864
+ ```ts
865
+ import { getUpdateFn } from "react-elmish/dist/Testing";
866
+
867
+ const update = getUpdateFn(updateMap);
868
+
869
+ // Call the update function in the test
870
+ const [model, cmd] = update(msg, model, props);
871
+ ```
872
+
873
+ A simple test:
884
874
 
885
875
  ```ts
886
876
  import * as Testing from "react-elmish/dist/Testing";
@@ -890,20 +880,18 @@ const createUpdateArgs = Testing.createUpdateArgsFactory(() => ({ /* initial mod
890
880
  ...
891
881
  it("returns the correct model and cmd", () => {
892
882
  // arrange
893
- const [msg, model, props] = createUpdateArgs(Shared.Msg.test(), { /* optionally override model here */ }, { /* optionally override props here */ });
894
-
895
- const expectedValue = // what you expect in the model
896
- const expectedCmds = [
897
- Shared.Msg.expectedMsg1("arg"),
898
- Shared.Msg.expectedMsg2(),
899
- ];
883
+ const args = createUpdateArgs(Msg.test(), { /* optionally override model here */ }, { /* optionally override props here */ });
900
884
 
901
885
  // act
902
- const [newModel, cmd] = Shared.update(model, msg, props);
886
+ // Call the update handler
887
+ const [newModel, cmd] = update(..args);
903
888
 
904
889
  // assert
905
- expect(newModel.value).toEqual(expectedValue);
906
- expect(Testing.getOfMsgParams(cmd)).toEqual(expectedCmds);
890
+ expect(newModel).toStrictEqual({ /* what you expect in the model */ });
891
+ expect(Testing.getOfMsgParams(cmd)).toEqual([
892
+ Msg.expectedMsg1("arg"),
893
+ Msg.expectedMsg2(),
894
+ ]);
907
895
  });
908
896
  ...
909
897
  ```
@@ -920,35 +908,22 @@ import * as Testing from "react-elmish/dist/Testing";
920
908
  ...
921
909
  it("returns the correct cmd", () => {
922
910
  // arrange
923
- const [msg, model, props] = createUpdateArgs(Shared.Msg.asyncTest());
911
+ const args = createUpdateArgs(Msg.asyncTest());
924
912
 
925
913
  // mock function which is called when the "AsyncTest" message is handled
926
914
  const functionMock = jest.fn();
927
915
 
928
916
  // act
929
- const [, cmd] = Shared.update(model, msg, props);
917
+ const [, cmd] = update(...args);
930
918
  const messages = await Testing.execCmd(cmd);
931
919
 
932
920
  // assert
933
921
  expect(functionMock).toBeCalled();
934
- expect(messages).toEqual([Shared.Msg.asyncTestSuccess()])
922
+ expect(messages).toEqual([Msg.asyncTestSuccess()])
935
923
  });
936
924
  ...
937
925
  ```
938
926
 
939
- ### Testing with an UpdateMap
940
-
941
- To test your update map, you can get an `update` function by calling `getUpdateFn`:
942
-
943
- ```ts
944
- import { getUpdateFn } from "react-elmish/dist/Testing";
945
-
946
- const update = getUpdateFn(updateMap);
947
-
948
- // in your test call update as usual
949
- const [model, cmd] = update(msg, model, props);
950
- ```
951
-
952
927
  ### UI Tests
953
928
 
954
929
  To test UI components with a fake model you can use `renderWithModel` from the Testing namespace. The first parameter is a function to render your component (e.g. with **@testing-library/react**). The second parameter is the fake model or an options object, where you can also pass a fake `dispatch` function.
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ // eslint-disable-next-line no-console
4
+ console.warn("react-elmish: Symbols for message names are deprecated now and will be removed in the next major version. Please convert all message names into strings.");
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "react-elmish",
3
- "version": "5.1.0",
3
+ "version": "5.1.1-beta.1",
4
4
  "description": "Elmish for React using Typescript",
5
5
  "author": "atheck",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
+ "install": "node installScript.js",
8
9
  "build": "npm run build:types && npm run build:js",
9
10
  "build:types": "tsc --emitDeclarationOnly --project ./src",
10
11
  "build:js": "babel src --out-dir dist --extensions \".ts,.tsx\" --ignore \"./**/*.spec.ts\",\"./**/*.spec.tsx\" --source-maps inline",