Skip to content

Commit 7510afd

Browse files
committed
functionality and usability improvements, code updates
- add support of function-type `validations` property. in this case, form calls this function and uses it's return value as object that contains validation rules - update validator's #nested function to check for inner form's ref on parent form itself first, before checking it in form.refs, since the latter becomes deprecated. - rely on `update-js` package for `attrs` updating, since corresponding code was extracted to this package - other minor updates
1 parent e54d0ca commit 7510afd

File tree

10 files changed

+59
-82
lines changed

10 files changed

+59
-82
lines changed

.babelrc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
presets: [
3-
'es2015',
4-
'react',
5-
'stage-1'
2+
"presets": [
3+
"es2015",
4+
"react",
5+
"stage-1"
66
]
77
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ lib
33
demo/node_modules
44
demo/dist
55

6+
.vscode
67
.idea

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
=== master
2+
Migrate from React.PropTypes to prop-types (by @AleksandrZhukov)
3+
4+
Fix flaw in readme code example (by @piton4eg)
25

36
More minor code style updates
47

demo/src/components/Source.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default class Source extends PureComponent {
1818

1919
componentDidUpdate() {
2020
if (this.state.open) {
21-
Prism.highlightElement(this.refs.prism);
21+
Prism.highlightElement(this.prism);
2222
}
2323
}
2424

@@ -39,7 +39,7 @@ export default class Source extends PureComponent {
3939
</div>
4040
<div className="source-code">
4141
{this.state.open &&
42-
<pre className="language-javascript" ref="prism">
42+
<pre className="language-javascript" ref={prism => this.prism = prism}>
4343
<code className="language-javascript">{children}</code>
4444
</pre>
4545
}

demo/src/forms/Form10.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const SOURCE = [['BaseForm.jsx', `
7575
{this.map('items', (_item, i) =>
7676
<div key={i}>
7777
<ItemForm
78-
ref={\`itemForm\${i}\`}
78+
ref={form => this[\`itemForm\${i}\`] = form}
7979
{...$.nested(\`items.\${i}\`)}
8080
validateOnChange
8181
/>
@@ -191,7 +191,7 @@ export default class Form10 extends BaseForm {
191191
{this.map('items', (_item, i) =>
192192
<div key={i} className="horizontal-container center bordered-form-item mb-20">
193193
<ItemForm
194-
ref={`itemForm${i}`}
194+
ref={form => this[`itemForm${i}`] = form}
195195
{...$.nested(`items.${i}`)}
196196
validateOnChange
197197
/>

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"build": "babel src --out-dir lib",
88
"clear": "rimraf lib",
99
"rebuild": "npm run clear && npm run build",
10-
"test": "mocha --compilers js:babel-register --recursive --require ./test/setup.js",
10+
"test": "mocha --compilers js:babel-register --require ./test/setup.js",
1111
"test:watch": "npm test -- --watch",
1212
"lint": "./node_modules/.bin/eslint src test demo/src --ext .js --ext .jsx"
1313
},
@@ -44,17 +44,18 @@
4444
"expect": "^1.20.2",
4545
"jsdom": "^9.8.0",
4646
"mocha": "^3.1.2",
47-
"prop-types": "^15.5.10",
47+
"prop-types": "^15.5.7",
4848
"react": "^15.3.2",
4949
"react-addons-test-utils": "^15.3.2",
5050
"react-dom": "^15.3.2",
5151
"rimraf": "^2.5.4"
5252
},
5353
"dependencies": {
5454
"lodash.get": "^4.4.2",
55-
"lodash.set": "^4.3.2"
55+
"update-js": "^1.0.0"
5656
},
5757
"peerDependencies": {
58+
"prop-types": "^15.5.7",
5859
"react": "^0.14.8 || >=15.0.0",
5960
"react-dom": "^0.14.8 || >=15.0.0"
6061
}

src/Form.jsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { PureComponent, Component } from 'react';
22
import PropTypes from 'prop-types';
33

4-
import { update, noop, buildFormValidator, buildHandlersCache } from './utils';
4+
import { noop, buildFormValidator, buildHandlersCache } from './utils';
5+
import update from 'update-js';
56
import get from 'lodash.get';
67

78
export default class Form extends (PureComponent || Component) {
@@ -25,7 +26,6 @@ export default class Form extends (PureComponent || Component) {
2526
};
2627

2728
state = { errors: {} };
28-
validations = {};
2929
validator = buildFormValidator(this);
3030
_handlersCache = buildHandlersCache();
3131

@@ -91,15 +91,15 @@ export default class Form extends (PureComponent || Component) {
9191
_setObject(obj) {
9292
return this._set((attrs, errors) => {
9393
for (const name in obj) {
94-
update(attrs, name, obj[name], false);
94+
update.in(attrs, name, obj[name]);
9595
this._updateErrors(errors, name, obj[name]);
9696
}
9797
});
9898
}
9999

100100
_setAttr(name, value) {
101101
return this._set((attrs, errors) => {
102-
update(attrs, name, value, false);
102+
update.in(attrs, name, value);
103103
this._updateErrors(errors, name, value);
104104
});
105105
}
@@ -133,7 +133,9 @@ export default class Form extends (PureComponent || Component) {
133133
}
134134

135135
get _validations() {
136-
return this.props.validations || this.validations;
136+
const validationz = this.props.validations || this.validations || {};
137+
138+
return typeof validationz === 'function' ? validationz.call(this) : validationz;
137139
}
138140

139141
ifValid(callback) {
@@ -181,7 +183,7 @@ export default class Form extends (PureComponent || Component) {
181183
const ary = this.get(name);
182184

183185
return this._set((attrs, errors) => {
184-
update(attrs, name, [...ary.slice(0, i), ...ary.slice(i + 1)]);
186+
update.in(attrs, name, [...ary.slice(0, i), ...ary.slice(i + 1)]);
185187
this._updateErrors(errors, `${name}.${i}`, null);
186188
});
187189
}

src/utils.js

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
/* global WeakMap */
2-
import set from 'lodash.set';
3-
42
export function noop(){}
53

64
export function bindState(component, key = 'form') {
@@ -10,33 +8,6 @@ export function bindState(component, key = 'form') {
108
};
119
}
1210

13-
export function update(obj, name, value) {
14-
_update(obj, name, value);
15-
16-
return obj;
17-
}
18-
19-
export function updated(obj, name, value) {
20-
const current = { ...obj };
21-
22-
return update(current, name, value);
23-
}
24-
25-
function _update(current, name, value) {
26-
const match = name.match(/^([\w\d]+)\.?(.+)?$/);
27-
const { 1: key, 2: rest } = match;
28-
29-
if (current[key] === undefined) {
30-
return set(current, name.split('.'), value);
31-
}
32-
if (!rest) {
33-
return current[key] = value;
34-
}
35-
36-
current[key] = Array.isArray(current[key]) ? [...current[key]] : { ...current[key] };
37-
_update(current[key], rest, value);
38-
}
39-
4011
function wildcard(name) {
4112
return name.replace(/\d+/g, '*');
4213
}
@@ -109,7 +80,8 @@ export function buildFormValidator(form) {
10980
return message;
11081
},
11182
nested(ref) {
112-
const errors = form.refs[ref].performValidation();
83+
const nestedForm = form[ref] || form.refs[ref];
84+
const errors = nestedForm.performValidation();
11385

11486
if (Object.getOwnPropertyNames(errors).length > 0) {
11587
this.addError(ref, 'invalid');

test/Form.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,38 @@ describe('<Form />', function() {
266266
'foo': 'cannot be blank'
267267
});
268268
});
269+
270+
context('when validations property is a function', function() {
271+
beforeEach(function() {
272+
TestForm = class extends Form {
273+
validations() {
274+
return {
275+
'foo'(value) {
276+
if (!value) return 'cannot be blank';
277+
}
278+
};
279+
};
280+
281+
render() {
282+
return (
283+
<div>
284+
<Input {...this.$('foo')} className="foo" />
285+
</div>
286+
);
287+
}
288+
};
289+
290+
this.currentTest.wrapper = mount(<Container />);
291+
});
292+
293+
it('calls validations function and performs validation according to result', function() {
294+
this.test.wrapper.instance().refs.form.performValidation();
295+
296+
expect(this.test.wrapper.instance().refs.form.state.errors).toMatch({
297+
'foo': 'cannot be blank'
298+
});
299+
});
300+
});
269301
});
270302

271303
context('common case with wildcards', function() {

test/utils.test.js

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,9 @@
11
import React, { Component } from 'react';
22
import Form from '../src/Form';
3-
import { bindState, updated, buildHandlersCache } from '../src/utils';
3+
import { bindState, buildHandlersCache } from '../src/utils';
44
import { shallow } from 'enzyme';
55
import expect from 'expect';
66

7-
describe('updated', function() {
8-
it('carefully sets deeply nested item: deeply nested array', function() {
9-
const obj = { foo: { bar: { baz: [1, 2, 3] } }, bak: { big: 1 } };
10-
const upd = updated(obj, 'foo.bar.baz.1', 4);
11-
12-
expect(obj === upd).toBe(false, 'obj should not be updated in place');
13-
expect(obj.foo === upd.foo).toBe(false, 'obj.foo should not be updated in place');
14-
expect(obj.foo.bar === upd.foo.bar).toBe(false, 'obj.foo.bar should not be updated in place');
15-
expect(obj.foo.bar.baz === upd.foo.bar.baz).toBe(false, 'obj.foo.bar.baz should not be updated in place');
16-
expect(obj.bak === upd.bak).toBe(true, 'obj.bak should not be cloned');
17-
expect(upd.foo.bar.baz).toMatch([1, 4, 3], 'value under desired name should be updated');
18-
});
19-
20-
it('carefully sets deeply nested item: deeply nested object', function() {
21-
const obj = { foo: { bar: [{ baz: 'baz1' }, { baz: 'baz2' }] }, bak: { big: 1 } };
22-
const upd = updated(obj, 'foo.bar.1.baz', 'baz3');
23-
24-
expect(obj === upd).toBe(false, 'obj should not be updated in place');
25-
expect(obj.foo === upd.foo).toBe(false, 'obj.foo should not be updated in place');
26-
expect(obj.foo.bar === upd.foo.bar).toBe(false, 'obj.foo.bar should not be updated in place');
27-
expect(obj.foo.bar[0] === upd.foo.bar[0]).toBe(true, 'obj.foo.bar items should not be cloned');
28-
expect(obj.bak === upd.bak).toBe(true, 'obj.bak should not be cloned');
29-
expect(upd.foo.bar[1]).toMatch({ baz: 'baz3' }, 'value under desired name should be updated');
30-
});
31-
32-
it('carefully sets deeply nested item, path collections are not defined', function() {
33-
const obj = { bak: { big: 1 } };
34-
const upd = updated(obj, 'foo.bar.baz.1', 4);
35-
36-
expect(obj.bak === upd.bak).toBe(true, 'obj.bak should not be cloned');
37-
expect(upd.foo.bar.baz).toMatch([undefined, 4], 'value under desired name should be updated');
38-
});
39-
});
40-
417
describe('bindState', function() {
428
class Page extends Component {
439
render() {

0 commit comments

Comments
 (0)