diff --git a/dist/components/Field.js b/dist/components/Field.js index 1a45412..6875ffc 100644 --- a/dist/components/Field.js +++ b/dist/components/Field.js @@ -51,7 +51,7 @@ var Field = function (_React$Component) { _createClass(Field, [{ key: 'componentWillUpdate', value: function componentWillUpdate(nextProps) { - if (nextProps.value !== this.props.value && nextProps.value !== this.finalValue) { + if (nextProps.value !== this.props.value && nextProps.value !== this.state.value) { this.cancelBroadcast(); this.setState({ value: nextProps.value }); this.finalValue = nextProps.value; @@ -65,7 +65,7 @@ var Field = function (_React$Component) { }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps) { - if (nextProps.value !== this.finalValue) return true; + if (nextProps.value !== this.state.value) return true; if (this.state.value !== this.finalValue) return true; if (this.props.match !== nextProps.match) return true; return false; @@ -138,7 +138,9 @@ var Field = function (_React$Component) { 'div', null, _react2.default.Children.map(this.props.children, function (child) { - return (0, _utilities.mapPropsToChild)(child, 'input', inputProps); + return (0, _utilities.mapPropsToChild)(child, 'input', function () { + return inputProps; + }); }) ); } diff --git a/dist/components/Form.js b/dist/components/Form.js index 3782455..e7656ed 100644 --- a/dist/components/Form.js +++ b/dist/components/Form.js @@ -40,7 +40,7 @@ var Form = function (_React$Component) { _this.state = {}; _react2.default.Children.map(props.children, function (child) { - return _this.addFieldsToState(child, false); + return _this.addFieldsToState(_this, child, false); }); return _this; } @@ -67,7 +67,7 @@ var Form = function (_React$Component) { var _this2 = this; _react2.default.Children.map(this.props.children, function (child) { - return _this2.addFieldsToState(child, true); + return _this2.addFieldsToState(_this2, child, true); }); } }, { @@ -79,7 +79,9 @@ var Form = function (_React$Component) { 'form', { onSubmit: this.onSubmit }, _react2.default.Children.map(this.props.children, function (child) { - return (0, _utilities.mapPropsToChild)(child, 'Field', (0, _utilities.makeFieldProps)(child, _this3.onFieldChange, _this3.state)); + return (0, _utilities.mapPropsToChild)(child, 'Field', function (grandChild) { + return (0, _utilities.makeFieldProps)(grandChild, _this3.onFieldChange, _this3.state); + }); }) ); } diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index 097b6b3..6ec1d2a 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -90,20 +90,20 @@ function buildStateForField(fieldProps) { return newState; } -function addFieldsToState(child) { - var mounted = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; +function addFieldsToState(component, child) { + var mounted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; if (typeof child.type === 'function' && child.type.name === 'Field') { var name = child.props.name; var fieldState = buildStateForField(child.props); if (mounted) { - this.setState(_defineProperty({}, name, fieldState)); + component.setState(_defineProperty({}, name, fieldState)); } else { - this.state[name] = fieldState; + component.state[name] = fieldState; // eslint-disable-line } } else if (child.props && child.props.children) { _react2.default.Children.forEach(child.props.children, function (nextChild) { - return addFieldsToState(nextChild, mounted); + return addFieldsToState(component, nextChild, mounted); }); } } @@ -124,12 +124,13 @@ function makeFieldProps(child, onChange, state) { return null; } -function mapPropsToChild(child, type, props) { +function mapPropsToChild(child, type, propFunction) { if (child.type === type || typeof child.type === 'function' && child.type.name === type) { - return _react2.default.cloneElement(child, props); - } else if (child.props && child.props.children) { + return _react2.default.cloneElement(child, propFunction(child)); + } + if (child.props && child.props.children) { var newChildren = _react2.default.Children.map(child.props.children, function (nestedChild) { - return mapPropsToChild(nestedChild, type, props); + return mapPropsToChild(nestedChild, type, propFunction); }); return _react2.default.cloneElement(child, null, newChildren); } diff --git a/dist/helpers/validators.js b/dist/helpers/validators.js index f8ea9a1..4f301f2 100644 --- a/dist/helpers/validators.js +++ b/dist/helpers/validators.js @@ -75,9 +75,8 @@ function alpha() { } function numeric() { - var numericRegex = /[^0-9\s]/i; return function (value) { - return typeof value === 'number' || typeof value === 'string' && !numericRegex.test(value); + return typeof value === 'number' || typeof value === 'string' && (!value || !value.replace(/([-+]{0,1})[0-9]+(\.[0-9]+)?([eE]([+-]{0,1})[0-9]+)?/, '')); }; } diff --git a/readme.md b/readme.md index 79cadd9..238e735 100644 --- a/readme.md +++ b/readme.md @@ -202,6 +202,8 @@ The `Field` component will behave as follows with respect to its children: > @param {String} [type='text'] - The input type of the wrapped input element. The input type for the wrapped input element. Defaults to `text`. + + *Note:* When number input is desired, it is preferred to use 'text' inputs with a `number` validator if it is expected that the user will enter `+`, `-`, or `e` characters. See `https://github.com/facebook/react/issues/1549`. #### `props[validator] = [validator]` > @param {\?} [validator=\?] - Optional. One or more validators to apply to the `Field`'s state. @@ -261,9 +263,9 @@ There are also a handful of different validators and properties (debounce, lengt This validates that the string input is comprised only of english alphabet characters and space characters. #### `props.number = numericValidation` -> @param {Boolean} [numericValidation=true] Optional. Will toggle validation for only numeric and space characters. +> @param {Boolean} [numericValidation=true] Optional. Will toggle validation for only numeric characters. - This validates that the string or number input is comprised only of numeric and space characters. + This validates that the string or number input is comprised only of numeric characters. This will allow appropriately placed `+`, `-`, `e`, and `.` characters. #### `props.max = maxValue` > @param {Number} maxValue - Validates an input field to be less than or equal to the maxValue. diff --git a/src/components/Field.jsx b/src/components/Field.jsx index 69970de..de0922a 100644 --- a/src/components/Field.jsx +++ b/src/components/Field.jsx @@ -29,7 +29,7 @@ const Field = class extends React.Component { } componentWillUpdate(nextProps) { - if ((nextProps.value !== this.props.value) && (nextProps.value !== this.finalValue)) { + if ((nextProps.value !== this.props.value) && (nextProps.value !== this.state.value)) { this.cancelBroadcast(); this.setState({ value: nextProps.value }); this.finalValue = nextProps.value; @@ -42,7 +42,7 @@ const Field = class extends React.Component { } shouldComponentUpdate(nextProps) { - if (nextProps.value !== this.finalValue) return true; + if (nextProps.value !== this.state.value) return true; if (this.state.value !== this.finalValue) return true; if (this.props.match !== nextProps.match) return true; return false; @@ -107,7 +107,7 @@ const Field = class extends React.Component { return (
{React.Children - .map(this.props.children, child => mapPropsToChild(child, 'input', inputProps))} + .map(this.props.children, child => mapPropsToChild(child, 'input', () => inputProps))}
); } diff --git a/src/components/Form.jsx b/src/components/Form.jsx index 18c7adb..d8f3ee6 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -12,7 +12,7 @@ const Form = class extends React.Component { this.state = {}; - React.Children.map(props.children, child => this.addFieldsToState(child, false)); + React.Children.map(props.children, child => this.addFieldsToState(this, child, false)); } onFieldChange({ name, value, valid, pristine }) { @@ -27,7 +27,7 @@ const Form = class extends React.Component { } reset() { - React.Children.map(this.props.children, child => this.addFieldsToState(child, true)); + React.Children.map(this.props.children, child => this.addFieldsToState(this, child, true)); } render() { @@ -35,7 +35,11 @@ const Form = class extends React.Component {
{React.Children .map(this.props.children, child => - mapPropsToChild(child, 'Field', makeFieldProps(child, this.onFieldChange, this.state)))} + mapPropsToChild( + child, + 'Field', + grandChild => makeFieldProps(grandChild, this.onFieldChange, this.state), + ))}
); } diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index eb3fffd..3557725 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -46,17 +46,20 @@ export function buildStateForField(fieldProps) { return newState; } -export function addFieldsToState(child, mounted = false) { +export function addFieldsToState(component, child, mounted = false) { if (typeof child.type === 'function' && child.type.name === 'Field') { const name = child.props.name; const fieldState = buildStateForField(child.props); if (mounted) { - this.setState({ [name]: fieldState }); + component.setState({ + [name]: fieldState, + }); } else { - this.state[name] = fieldState; + component.state[name] = fieldState; // eslint-disable-line } } else if (child.props && child.props.children) { - React.Children.forEach(child.props.children, nextChild => addFieldsToState(nextChild, mounted)); + React.Children.forEach(child.props.children, + nextChild => addFieldsToState(component, nextChild, mounted)); } } @@ -72,12 +75,13 @@ export function makeFieldProps(child, onChange, state) { return null; } -export function mapPropsToChild(child, type, props) { +export function mapPropsToChild(child, type, propFunction) { if (child.type === type || (typeof child.type === 'function' && child.type.name === type)) { - return React.cloneElement(child, props); - } else if (child.props && child.props.children) { + return React.cloneElement(child, propFunction(child)); + } + if (child.props && child.props.children) { const newChildren = React.Children.map(child.props.children, nestedChild => ( - mapPropsToChild(nestedChild, type, props))); + mapPropsToChild(nestedChild, type, propFunction))); return React.cloneElement(child, null, newChildren); } return child; diff --git a/src/helpers/validators.jsx b/src/helpers/validators.jsx index e291360..effc906 100644 --- a/src/helpers/validators.jsx +++ b/src/helpers/validators.jsx @@ -48,12 +48,13 @@ export function alpha() { } export function numeric() { - const numericRegex = /[^0-9\s]/i; - return value => typeof value === 'number' || (typeof value === 'string' && !numericRegex.test(value)); + return value => typeof value === 'number' || (typeof value === 'string' && + (!value || !value.replace(/([-+]{0,1})[0-9]+(\.[0-9]+)?([eE]([+-]{0,1})[0-9]+)?/, ''))); } export function max(criteria) { - return value => ((typeof value === 'string' && value) || typeof value === 'number') && (Number(value) <= Number(criteria)); + return value => ((typeof value === 'string' && value) || typeof value === 'number') && + (Number(value) <= Number(criteria)); } export function min(criteria) { diff --git a/tests/spec_helpers/index.js b/tests/spec_helpers/index.js index 97fbc6a..be461f7 100644 --- a/tests/spec_helpers/index.js +++ b/tests/spec_helpers/index.js @@ -6,7 +6,7 @@ export function updateInput(DOM, value = '', type = 'text') { DOM.find('input').simulate('change', { target: { value }, type }); } -export function buildField(mountingFunction, validator, value, type) { +export function buildField(mountingFunction, validator, value, type="text") { const validatorToObj = { [validator]: value, }; diff --git a/tests/utilities/validators.spec.js b/tests/utilities/validators.spec.js index 551d8c2..8a04e90 100644 --- a/tests/utilities/validators.spec.js +++ b/tests/utilities/validators.spec.js @@ -1,4 +1,4 @@ -/* globals describe it before beforeEach after afterEach */ +/* globals describe it before beforeEach after afterEach xit */ import { expect } from 'chai'; // eslint-disable-line import sinon from 'sinon'; // eslint-disable-line import { shallow, mount } from 'enzyme'; // eslint-disable-line @@ -260,38 +260,47 @@ describe('Validator Functionality', () => { const wrapper = buildField(mount, 'number', true); const valFunc = validators.numeric(); - it('returns `true` for strings consisting of only number and space characters', () => { + it('returns `true` for strings consisting of only number characters', () => { expect(valFunc('')).to.equal(true); + expect(valFunc('4e-10')).to.equal(true); + expect(valFunc('0.5E+10')).to.equal(true); expect(valFunc('0123')).to.equal(true); + expect(valFunc(123e-10)).to.equal(true); + expect(valFunc(456e-10)).to.equal(true); expect(valFunc(123)).to.equal(true); - expect(valFunc('123 123 123')).to.equal(true); - expect(valFunc('\t\n ')).to.equal(true); }); it('returns `false` for text inputs with non-numeric/non-space characters', () => { + expect(valFunc('\t\n ')).to.equal(false); expect(valFunc('_')).to.equal(false); expect(valFunc('123!')).to.equal(false); expect(valFunc('890*')).to.equal(false); + expect(valFunc('123 123 123')).to.equal(false); + expect(valFunc('1234-1234-1234-1234')).to.equal(false); expect(valFunc('$112233')).to.equal(false); expect(valFunc('$!\.')).to.equal(false); expect(valFunc('Is this valid?')).to.equal(false); }); it('is properly used by a `Field` component to validate', () => { - expect(wrapper.state()).to.have.property('valid', false); expect(wrapper.state()).to.have.property('value', ''); + expect(wrapper.state()).to.have.property('valid', false); updateInput(wrapper, '12345\t12345 12345'); - expect(wrapper.state()).to.have.property('valid', true); expect(wrapper.state()).to.have.property('value', '12345\t12345 12345'); + expect(wrapper.state()).to.have.property('valid', false); updateInput(wrapper, '1234-1234-1234-1234'); - expect(wrapper.state()).to.have.property('valid', false); expect(wrapper.state()).to.have.property('value', '1234-1234-1234-1234'); + expect(wrapper.state()).to.have.property('valid', false); updateInput(wrapper, 123, 'number'); expect(wrapper.state()).to.have.property('valid', true); expect(wrapper.state()).to.have.property('value', 123); + + updateInput(wrapper, 0.5e3, 'number'); + expect(wrapper.state()).to.have.property('valid', true); + expect(wrapper.state()).to.have.property('value', 0.5e3); }); });