├── .babelrc ├── .flowconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__ ├── Context-test.js ├── ReComponent-test.js ├── RePureComponent-test.js ├── UpdateTypes-test.js ├── __snapshots__ │ ├── ReComponent-test.js.snap │ └── UpdateTypes-test.js.snap └── helpers.js ├── dist ├── react-recomponent.js.flow ├── react-recomponent.m.js.flow └── react-recomponent.umd.js.flow ├── package.json ├── src ├── index.js ├── re.js └── update-types.js ├── type-definitions ├── ReComponent.d.ts ├── ReComponent.js.flow ├── __tests__ │ └── ReComponent.js └── ts-tests │ ├── exports.ts │ ├── index.d.ts │ ├── tsconfig.json │ ├── tslint.json │ └── updateTypes.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [options] 2 | suppress_comment=\\(.\\|\n\\)*\\$ExpectError 3 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe 4 | include_warnings=true 5 | 6 | [ignore] 7 | node_modules/.* 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | coverage/ 4 | dist/*.js 5 | dist/*.ts 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | install: 5 | - "yarn global add codecov" 6 | - "yarn install --freeze-lockfile" 7 | script: 8 | - "yarn test:js --coverage" 9 | - "yarn prettier --list-different" 10 | - "yarn test:types:flow" 11 | - "yarn test:types:ts" 12 | - "codecov" 13 | cache: yarn -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Philipp Spieß 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
8 | Reason-style reducer components for React using ES6 classes. 9 |
10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Loading...
} 241 |242 | {this.state.result ? this.state.result : 'Click "Fetch" to start'} 243 |
244 |extends Component
{
54 | static reducer extends ReComponent {}
67 |
--------------------------------------------------------------------------------
/type-definitions/ReComponent.js.flow:
--------------------------------------------------------------------------------
1 | /**
2 | * This file contains the Flow type definitions for ReComponent.
3 | *
4 | * @flow
5 | */
6 |
7 | import * as React from "react";
8 |
9 | declare opaque type UpdateType;
10 |
11 | declare export function NoUpdate(): {| type: UpdateType |};
12 | declare export function Update {}
50 |
--------------------------------------------------------------------------------
/type-definitions/__tests__/ReComponent.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import * as React from "react";
4 |
5 | import {
6 | ReComponent,
7 | Update,
8 | NoUpdate,
9 | SideEffects,
10 | UpdateWithSideEffects
11 | } from "../../";
12 |
13 | type Action =
14 | | {| type: "A" |}
15 | | {| type: "B" |}
16 | | {| type: "C" |}
17 | | {| type: "D" |};
18 |
19 | class StateMismatch extends ReComponent<{}, { count: number }, Action> {
20 | // $ExpectError
21 | state = { invalid: "state" };
22 |
23 | static reducer(action, state) {
24 | switch (action.type) {
25 | case "A":
26 | return Update({});
27 | case "B":
28 | return Update({ count: 1 });
29 | case "C":
30 | // $ExpectError - `count` should be `number`
31 | return Update({ count: "1" });
32 | default:
33 | // $ExpectError - `invalid` is missing in State
34 | return Update({ invalid: "state" });
35 | }
36 | }
37 | }
38 |
39 | class UpdateTypes extends ReComponent<{}, { count: number }, Action> {
40 | // Used to test the callback property of SideEffects
41 | someClassProperty: number;
42 |
43 | static reducer(action, state) {
44 | switch (action.type) {
45 | case "A":
46 | return NoUpdate();
47 | case "B":
48 | return Update({ count: 1 });
49 | case "C":
50 | return SideEffects((instance: UpdateTypes) => {
51 | instance.someClassProperty = 1;
52 | // $ExpectError - `instance.someClassProperty` has to be number
53 | instance.someClassProperty = "1";
54 | });
55 | default:
56 | return UpdateWithSideEffects({ count: 1 }, (instance: UpdateTypes) => {
57 | instance.someClassProperty = 1;
58 | // $ExpectError - `instance.someClassProperty` has to be number
59 | instance.someClassProperty = "1";
60 | });
61 | }
62 | }
63 | }
64 |
65 | class TypedActionTypes extends ReComponent<
66 | {},
67 | { count: number },
68 | {| type: "CLICK" |}
69 | > {
70 | handleClick = () => this.send({ type: "CLICK" });
71 |
72 | static reducer(action, state) {
73 | switch (action.type) {
74 | case "CLICK":
75 | return NoUpdate();
76 | default:
77 | return NoUpdate();
78 | }
79 | }
80 | }
81 |
82 | const typedActionTypes = new TypedActionTypes();
83 | typedActionTypes.send({ type: "CLICK" });
84 | // $ExpectError - "CLACK" is invalid action type
85 | typedActionTypes.send({ type: "CLACK" });
86 | // $ExpectError - invalid action
87 | typedActionTypes.send({});
88 |
89 | typedActionTypes.handleClick();
90 | // $ExpectError - `handleClick` expects no arguments
91 | typedActionTypes.handleClick({});
92 | // $ExpectError - `handleClick` expects no arguments
93 | typedActionTypes.handleClick(1);
94 |
95 | // Flow can verify that we've handled every defined action type for us through
96 | // what is called [exhaustiveness testing].
97 | //
98 | // This can be done by using the special type `empty` and casting to it in the
99 | // `default` or `else` branch. This will fail once Flow determines it can be
100 | // reached.
101 | //
102 | // [exhaustiveness testing]: https://blog.jez.io/flow-exhaustiveness/
103 | const absurd = (state: S): {| type: UpdateType, state: S |};
13 | declare export function SideEffects(
17 | state: S,
18 | sideEffects: (T) => mixed
19 | ): {| type: UpdateType, state: S, sideEffects: T => mixed |};
20 |
21 | declare export class ReComponent<
22 | Props,
23 | State,
24 | Action: { +type: string }
25 | > extends React.Component(state: S, sideEffects: SideEffect
15 | ReComponent; // $ExpectType typeof ReComponent
16 | RePureComponent; // $ExpectType typeof RePureComponent
17 |
18 | recomponent.NoUpdate; // $ExpectType () => NoUpdateAction
19 | recomponent.Update; // $ExpectType (state: S, sideEffects: SideEffect
22 | recomponent.ReComponent; // $ExpectType typeof ReComponent
23 | recomponent.RePureComponent; // $ExpectType typeof RePureComponent
24 |
--------------------------------------------------------------------------------
/type-definitions/ts-tests/index.d.ts:
--------------------------------------------------------------------------------
1 | // TypeScript Version: 2.3
2 |
--------------------------------------------------------------------------------
/type-definitions/ts-tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "sourceMap": true,
6 | "noImplicitAny": true,
7 | "noImplicitThis": true,
8 | "strictFunctionTypes": true,
9 | "strictNullChecks": true,
10 | "lib": ["esnext"],
11 | "baseUrl": "../../",
12 | "paths": { "react-recomponent": ["."] }
13 | },
14 | "exclude": [
15 | "node_modules"
16 | ]
17 | }
--------------------------------------------------------------------------------
/type-definitions/ts-tests/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "dtslint/dtslint.json",
3 | "rules": {
4 | "no-useless-files": false,
5 | "no-duplicate-imports": false
6 | }
7 | }
--------------------------------------------------------------------------------
/type-definitions/ts-tests/updateTypes.ts:
--------------------------------------------------------------------------------
1 | import {
2 | NoUpdate,
3 | Update,
4 | SideEffects,
5 | UpdateWithSideEffects
6 | } from "react-recomponent";
7 |
8 | // $ExpectType NoUpdateAction
9 | NoUpdate();
10 |
11 | // $ExpectType UpdateAction