= { 8 | [Key in K]: P[Key] | Mutant
9 | }; 10 | 11 | export function withMutantProps
( 12 | Comp: CClass
,
13 | ...names: Array
17 | > {
18 | constructor(props: any) {
19 | super(props);
20 | const initialState: any = {};
21 | for (let n = names.length, i = 0; i < n; i++) {
22 | initialState[names[i]] = null;
23 | }
24 | this.state = initialState;
25 | }
26 |
27 | private _watchers: any;
28 |
29 | public componentDidMount() {
30 | const props = this.props as MutantProps ;
31 | this._watchers = {};
32 | names.forEach(name => {
33 | this._watchers[name] = watch(props[name], (val: any) => {
34 | this.setState((prev: any) => ({...prev, [name]: val}));
35 | });
36 | });
37 | }
38 |
39 | public componentWillUnmount() {
40 | let name: string;
41 | for (let n = names.length, i = 0; i < n; i++) {
42 | name = names[i];
43 | if (this._watchers[name]) {
44 | this._watchers[name]();
45 | }
46 | }
47 | this._watchers = null;
48 | }
49 |
50 | public render() {
51 | const props: P = {...this.props as any, ...this.state as any};
52 | return createElement(Comp, props, this.props.children);
53 | }
54 | };
55 | WMP.displayName =
56 | 'WithMutantProps(' +
57 | (Comp.displayName || (Comp as any).name || 'Component') +
58 | ')';
59 | return WMP;
60 | }
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-mutant-hoc",
3 | "version": "1.1.0",
4 | "description": "A utility to make React components easily consume Mutant observables",
5 | "main": "index.js",
6 | "typings": "index.d.ts",
7 | "scripts": {
8 | "compile": "tsc",
9 | "test": "tape test.js",
10 | "prepublishOnly": "npm run compile"
11 | },
12 | "author": "staltz.com",
13 | "license": "MIT",
14 | "dependencies": {
15 | "@types/react": ">=16.0.0",
16 | "mutant": ">=3.21.0",
17 | "react": ">=16.0.0"
18 | },
19 | "devDependencies": {
20 | "@types/node": "^8.0.30",
21 | "tape": "^4.8.0",
22 | "typescript": "2.6.x",
23 | "react-test-renderer": "^16.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | const test = require('tape');
2 | const React = require('React');
3 | const Value = require('mutant/value');
4 | const {withMutantProps} = require('./index');
5 | const TestRenderer = require('react-test-renderer');
6 |
7 | test('updates component state when mutant stream updates', t => {
8 | class Input extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | }
12 | render() {
13 | return React.createElement('span', null, `My age is ${this.props.age}`);
14 | }
15 | }
16 |
17 | const Output = withMutantProps(Input, 'age');
18 |
19 | const obs = Value();
20 | obs.set(20);
21 |
22 | const elem = React.createElement(Output, {age: obs});
23 | const testRenderer = TestRenderer.create(elem);
24 |
25 | const result1 = testRenderer.toJSON();
26 | t.ok(result1, 'should have rendered');
27 | t.equal(result1.children.length, 1, 'should have one child');
28 | t.equal(result1.children[0], 'My age is 20', 'should show 20');
29 |
30 | obs.set(21);
31 | testRenderer.update(elem);
32 |
33 | const result2 = testRenderer.toJSON();
34 | t.ok(result2, 'should have rendered');
35 | t.equal(result2.children.length, 1, 'should have one child');
36 | t.equal(result2.children[0], 'My age is 21', 'should show 21');
37 |
38 | t.end();
39 | });
40 |
41 | test('supports many mutant streams', t => {
42 | class Input extends React.Component {
43 | constructor(props) {
44 | super(props);
45 | }
46 | render() {
47 | return React.createElement('span', null, `${this.props.lat},${this.props.lng}`);
48 | }
49 | }
50 |
51 | const Output = withMutantProps(Input, 'lat', 'lng');
52 |
53 | const lat = Value();
54 | const lng = Value();
55 | lat.set(45);
56 | lng.set(30);
57 |
58 | const elem = React.createElement(Output, {lat, lng});
59 | const testRenderer = TestRenderer.create(elem);
60 |
61 | const result1 = testRenderer.toJSON();
62 | t.ok(result1, 'should have rendered');
63 | t.equal(result1.children.length, 1, 'should have one child');
64 | t.equal(result1.children[0], '45,30', 'should show 45,30');
65 |
66 | lat.set(46);
67 | testRenderer.update(elem);
68 |
69 | const result2 = testRenderer.toJSON();
70 | t.ok(result2, 'should have rendered');
71 | t.equal(result2.children.length, 1, 'should have one child');
72 | t.equal(result2.children[0], '46,30', 'should show 46,30');
73 |
74 | lng.set(32);
75 | testRenderer.update(elem);
76 |
77 | const result3 = testRenderer.toJSON();
78 | t.ok(result3, 'should have rendered');
79 | t.equal(result3.children.length, 1, 'should have one child');
80 | t.equal(result3.children[0], '46,32', 'should show 46,32');
81 |
82 | t.end();
83 | });
84 |
85 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "sourceMap": true,
7 | "outDir": "./",
8 | "strict": true
9 | },
10 | "files": ["index.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@types/node@^8.0.30":
6 | version "8.0.57"
7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.57.tgz#e5d8b4dc112763e35cfc51988f4f38da3c486d99"
8 |
9 | "@types/react@>=16.0.0":
10 | version "16.0.28"
11 | resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.28.tgz#eb0b31272528da8f20477ec27569c4f767315b33"
12 |
13 | asap@~2.0.3:
14 | version "2.0.6"
15 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
16 |
17 | balanced-match@^1.0.0:
18 | version "1.0.0"
19 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
20 |
21 | brace-expansion@^1.1.7:
22 | version "1.1.8"
23 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
24 | dependencies:
25 | balanced-match "^1.0.0"
26 | concat-map "0.0.1"
27 |
28 | browser-split@0.0.1:
29 | version "0.0.1"
30 | resolved "https://registry.yarnpkg.com/browser-split/-/browser-split-0.0.1.tgz#7b097574f8e3ead606fb4664e64adfdda2981a93"
31 |
32 | concat-map@0.0.1:
33 | version "0.0.1"
34 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
35 |
36 | core-js@^1.0.0:
37 | version "1.2.7"
38 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
39 |
40 | deep-equal@~1.0.1:
41 | version "1.0.1"
42 | resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
43 |
44 | define-properties@^1.1.2:
45 | version "1.1.2"
46 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
47 | dependencies:
48 | foreach "^2.0.5"
49 | object-keys "^1.0.8"
50 |
51 | defined@~1.0.0:
52 | version "1.0.0"
53 | resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
54 |
55 | encoding@^0.1.11:
56 | version "0.1.12"
57 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
58 | dependencies:
59 | iconv-lite "~0.4.13"
60 |
61 | es-abstract@^1.5.0:
62 | version "1.10.0"
63 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
64 | dependencies:
65 | es-to-primitive "^1.1.1"
66 | function-bind "^1.1.1"
67 | has "^1.0.1"
68 | is-callable "^1.1.3"
69 | is-regex "^1.0.4"
70 |
71 | es-to-primitive@^1.1.1:
72 | version "1.1.1"
73 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
74 | dependencies:
75 | is-callable "^1.1.1"
76 | is-date-object "^1.0.1"
77 | is-symbol "^1.0.1"
78 |
79 | fbjs@^0.8.16:
80 | version "0.8.16"
81 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
82 | dependencies:
83 | core-js "^1.0.0"
84 | isomorphic-fetch "^2.1.1"
85 | loose-envify "^1.0.0"
86 | object-assign "^4.1.0"
87 | promise "^7.1.1"
88 | setimmediate "^1.0.5"
89 | ua-parser-js "^0.7.9"
90 |
91 | for-each@~0.3.2:
92 | version "0.3.2"
93 | resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"
94 | dependencies:
95 | is-function "~1.0.0"
96 |
97 | foreach@^2.0.5:
98 | version "2.0.5"
99 | resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
100 |
101 | fs.realpath@^1.0.0:
102 | version "1.0.0"
103 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
104 |
105 | function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.0:
106 | version "1.1.1"
107 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
108 |
109 | glob@~7.1.2:
110 | version "7.1.2"
111 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
112 | dependencies:
113 | fs.realpath "^1.0.0"
114 | inflight "^1.0.4"
115 | inherits "2"
116 | minimatch "^3.0.4"
117 | once "^1.3.0"
118 | path-is-absolute "^1.0.0"
119 |
120 | has@^1.0.1, has@~1.0.1:
121 | version "1.0.1"
122 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
123 | dependencies:
124 | function-bind "^1.0.2"
125 |
126 | iconv-lite@~0.4.13:
127 | version "0.4.19"
128 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
129 |
130 | inflight@^1.0.4:
131 | version "1.0.6"
132 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
133 | dependencies:
134 | once "^1.3.0"
135 | wrappy "1"
136 |
137 | inherits@2, inherits@~2.0.3:
138 | version "2.0.3"
139 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
140 |
141 | is-callable@^1.1.1, is-callable@^1.1.3:
142 | version "1.1.3"
143 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
144 |
145 | is-date-object@^1.0.1:
146 | version "1.0.1"
147 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
148 |
149 | is-function@~1.0.0:
150 | version "1.0.1"
151 | resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
152 |
153 | is-regex@^1.0.4:
154 | version "1.0.4"
155 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
156 | dependencies:
157 | has "^1.0.1"
158 |
159 | is-stream@^1.0.1:
160 | version "1.1.0"
161 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
162 |
163 | is-symbol@^1.0.1:
164 | version "1.0.1"
165 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
166 |
167 | isomorphic-fetch@^2.1.1:
168 | version "2.2.1"
169 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
170 | dependencies:
171 | node-fetch "^1.0.1"
172 | whatwg-fetch ">=0.10.0"
173 |
174 | js-tokens@^3.0.0:
175 | version "3.0.2"
176 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
177 |
178 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
179 | version "1.3.1"
180 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
181 | dependencies:
182 | js-tokens "^3.0.0"
183 |
184 | minimatch@^3.0.4:
185 | version "3.0.4"
186 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
187 | dependencies:
188 | brace-expansion "^1.1.7"
189 |
190 | minimist@~1.2.0:
191 | version "1.2.0"
192 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
193 |
194 | mutant@>=3.21.0:
195 | version "3.22.1"
196 | resolved "https://registry.yarnpkg.com/mutant/-/mutant-3.22.1.tgz#90487546f700b3c28aa80a43d1cf7d338f307581"
197 | dependencies:
198 | browser-split "0.0.1"
199 | xtend "^4.0.1"
200 |
201 | node-fetch@^1.0.1:
202 | version "1.7.3"
203 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
204 | dependencies:
205 | encoding "^0.1.11"
206 | is-stream "^1.0.1"
207 |
208 | object-assign@^4.1.0, object-assign@^4.1.1:
209 | version "4.1.1"
210 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
211 |
212 | object-inspect@~1.3.0:
213 | version "1.3.0"
214 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.3.0.tgz#5b1eb8e6742e2ee83342a637034d844928ba2f6d"
215 |
216 | object-keys@^1.0.8:
217 | version "1.0.11"
218 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
219 |
220 | once@^1.3.0:
221 | version "1.4.0"
222 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
223 | dependencies:
224 | wrappy "1"
225 |
226 | path-is-absolute@^1.0.0:
227 | version "1.0.1"
228 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
229 |
230 | path-parse@^1.0.5:
231 | version "1.0.5"
232 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
233 |
234 | promise@^7.1.1:
235 | version "7.3.1"
236 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
237 | dependencies:
238 | asap "~2.0.3"
239 |
240 | prop-types@^15.6.0:
241 | version "15.6.0"
242 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
243 | dependencies:
244 | fbjs "^0.8.16"
245 | loose-envify "^1.3.1"
246 | object-assign "^4.1.1"
247 |
248 | react-test-renderer@^16.0.0:
249 | version "16.2.0"
250 | resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211"
251 | dependencies:
252 | fbjs "^0.8.16"
253 | object-assign "^4.1.1"
254 | prop-types "^15.6.0"
255 |
256 | react@>=16.0.0:
257 | version "16.2.0"
258 | resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
259 | dependencies:
260 | fbjs "^0.8.16"
261 | loose-envify "^1.1.0"
262 | object-assign "^4.1.1"
263 | prop-types "^15.6.0"
264 |
265 | resolve@~1.4.0:
266 | version "1.4.0"
267 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
268 | dependencies:
269 | path-parse "^1.0.5"
270 |
271 | resumer@~0.0.0:
272 | version "0.0.0"
273 | resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759"
274 | dependencies:
275 | through "~2.3.4"
276 |
277 | setimmediate@^1.0.5:
278 | version "1.0.5"
279 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
280 |
281 | string.prototype.trim@~1.1.2:
282 | version "1.1.2"
283 | resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
284 | dependencies:
285 | define-properties "^1.1.2"
286 | es-abstract "^1.5.0"
287 | function-bind "^1.0.2"
288 |
289 | tape@^4.8.0:
290 | version "4.8.0"
291 | resolved "https://registry.yarnpkg.com/tape/-/tape-4.8.0.tgz#f6a9fec41cc50a1de50fa33603ab580991f6068e"
292 | dependencies:
293 | deep-equal "~1.0.1"
294 | defined "~1.0.0"
295 | for-each "~0.3.2"
296 | function-bind "~1.1.0"
297 | glob "~7.1.2"
298 | has "~1.0.1"
299 | inherits "~2.0.3"
300 | minimist "~1.2.0"
301 | object-inspect "~1.3.0"
302 | resolve "~1.4.0"
303 | resumer "~0.0.0"
304 | string.prototype.trim "~1.1.2"
305 | through "~2.3.8"
306 |
307 | through@~2.3.4, through@~2.3.8:
308 | version "2.3.8"
309 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
310 |
311 | typescript@2.6.x:
312 | version "2.6.2"
313 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
314 |
315 | ua-parser-js@^0.7.9:
316 | version "0.7.17"
317 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
318 |
319 | whatwg-fetch@>=0.10.0:
320 | version "2.0.3"
321 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
322 |
323 | wrappy@1:
324 | version "1.0.2"
325 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
326 |
327 | xtend@^4.0.1:
328 | version "4.0.1"
329 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
330 |
--------------------------------------------------------------------------------