From 75631b6777e2a43db488548ccfb6fa452cd3161e Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 25 Feb 2017 21:07:05 -0600 Subject: [PATCH 01/15] Refactored mapPropsToChild to accept a mapping of child types & props to sets of props to pass to the child --- src/helpers/utilities.jsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index 3557725..aabb2bf 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -75,14 +75,18 @@ export function makeFieldProps(child, onChange, state) { return null; } -export function mapPropsToChild(child, type, propFunction) { - if (child.type === type || (typeof child.type === 'function' && child.type.name === type)) { - return React.cloneElement(child, propFunction(child)); - } +export function mapPropsToChild(child, propsForChildType) { + const type = (child.type === 'function') ? child.type.name : child.type; + const childProps = {}; + + if (child.props.valid) Object.assign(childProps, propsForChildType.valid); + if (child.props.pristine) Object.assign(childProps, propsForChildType.pristine); + if (type === 'Field') return React.cloneElement(child, propsForChildType.Field(child)); + if (child.props && child.props.children) { const newChildren = React.Children.map(child.props.children, nestedChild => ( - mapPropsToChild(nestedChild, type, propFunction))); - return React.cloneElement(child, null, newChildren); + mapPropsToChild(nestedChild, propsForChildType))); + return React.cloneElement(child, childProps, newChildren); } return child; } From b55a571747920f615e5cf6f43294eb00cf2572c7 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 25 Feb 2017 21:09:58 -0600 Subject: [PATCH 02/15] Modified Field and Form components to adhere to new API for mapPropsToChild --- dist/helpers/utilities.js | 16 ++++++++++------ src/components/Field.jsx | 2 +- src/components/Form.jsx | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index 6ec1d2a..d7f40e1 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -124,15 +124,19 @@ function makeFieldProps(child, onChange, state) { return null; } -function mapPropsToChild(child, type, propFunction) { - if (child.type === type || typeof child.type === 'function' && child.type.name === type) { - return _react2.default.cloneElement(child, propFunction(child)); - } +function mapPropsToChild(child, propsForChildType) { + var type = child.type === 'function' ? child.type.name : child.type; + var childProps = {}; + + if (child.props.valid) Object.assign(childProps, propsForChildType.valid); + if (child.props.pristine) Object.assign(childProps, propsForChildType.pristine); + if (type === 'Field') return _react2.default.cloneElement(child, propsForChildType.Field(child)); + if (child.props && child.props.children) { var newChildren = _react2.default.Children.map(child.props.children, function (nestedChild) { - return mapPropsToChild(nestedChild, type, propFunction); + return mapPropsToChild(nestedChild, propsForChildType); }); - return _react2.default.cloneElement(child, null, newChildren); + return _react2.default.cloneElement(child, childProps, newChildren); } return child; } \ No newline at end of file diff --git a/src/components/Field.jsx b/src/components/Field.jsx index de0922a..b4df51a 100644 --- a/src/components/Field.jsx +++ b/src/components/Field.jsx @@ -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 d8f3ee6..54c5e44 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -37,9 +37,9 @@ const Form = class extends React.Component { .map(this.props.children, child => mapPropsToChild( child, - 'Field', - grandChild => makeFieldProps(grandChild, this.onFieldChange, this.state), - ))} + { Field: grandChild => makeFieldProps(grandChild, this.onFieldChange, this.state) }, + ), + )} ); } From d76e3f1a59c671c82ca63cdca4336d6fcb71a4d8 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 25 Feb 2017 21:29:14 -0600 Subject: [PATCH 03/15] Bugfixed updated mapPropsToChild (it wasn't properly determining child type --- dist/components/Field.js | 6 +++--- dist/components/Form.js | 6 +++--- dist/helpers/utilities.js | 22 +++++++++++++++------- src/helpers/utilities.jsx | 22 +++++++++++++++------- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/dist/components/Field.js b/dist/components/Field.js index 6875ffc..c5a6cb5 100644 --- a/dist/components/Field.js +++ b/dist/components/Field.js @@ -138,9 +138,9 @@ var Field = function (_React$Component) { 'div', null, _react2.default.Children.map(this.props.children, function (child) { - return (0, _utilities.mapPropsToChild)(child, 'input', function () { - return inputProps; - }); + return (0, _utilities.mapPropsToChild)(child, { input: function input() { + return inputProps; + } }); }) ); } diff --git a/dist/components/Form.js b/dist/components/Form.js index e7656ed..670b226 100644 --- a/dist/components/Form.js +++ b/dist/components/Form.js @@ -79,9 +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', function (grandChild) { - return (0, _utilities.makeFieldProps)(grandChild, _this3.onFieldChange, _this3.state); - }); + return (0, _utilities.mapPropsToChild)(child, { Field: function Field(grandChild) { + return (0, _utilities.makeFieldProps)(grandChild, _this3.onFieldChange, _this3.state); + } }); }) ); } diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index d7f40e1..a664f66 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -124,17 +124,25 @@ function makeFieldProps(child, onChange, state) { return null; } -function mapPropsToChild(child, propsForChildType) { - var type = child.type === 'function' ? child.type.name : child.type; +function mapPropsToChild(child, childPropsMap) { + var type = typeof child.type === 'function' ? child.type.name : child.type; var childProps = {}; - if (child.props.valid) Object.assign(childProps, propsForChildType.valid); - if (child.props.pristine) Object.assign(childProps, propsForChildType.pristine); - if (type === 'Field') return _react2.default.cloneElement(child, propsForChildType.Field(child)); - + if (childPropsMap.valid && child.props.valid) { + Object.assign(childProps, childPropsMap.valid); + } + if (childPropsMap.pristine && child.props.pristine) { + Object.assign(childProps, childPropsMap.pristine); + } + if (childPropsMap.Field && type === 'Field') { + return _react2.default.cloneElement(child, childPropsMap.Field(child)); + } + if (childPropsMap.input && type === 'input') { + return _react2.default.cloneElement(child, childPropsMap.input(child)); + } if (child.props && child.props.children) { var newChildren = _react2.default.Children.map(child.props.children, function (nestedChild) { - return mapPropsToChild(nestedChild, propsForChildType); + return mapPropsToChild(nestedChild, childPropsMap); }); return _react2.default.cloneElement(child, childProps, newChildren); } diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index aabb2bf..6905969 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -75,17 +75,25 @@ export function makeFieldProps(child, onChange, state) { return null; } -export function mapPropsToChild(child, propsForChildType) { - const type = (child.type === 'function') ? child.type.name : child.type; +export function mapPropsToChild(child, childPropsMap) { + const type = (typeof child.type === 'function') ? child.type.name : child.type; const childProps = {}; - if (child.props.valid) Object.assign(childProps, propsForChildType.valid); - if (child.props.pristine) Object.assign(childProps, propsForChildType.pristine); - if (type === 'Field') return React.cloneElement(child, propsForChildType.Field(child)); - + if (childPropsMap.valid && child.props.valid) { + Object.assign(childProps, childPropsMap.valid); + } + if (childPropsMap.pristine && child.props.pristine) { + Object.assign(childProps, childPropsMap.pristine); + } + if (childPropsMap.Field && type === 'Field') { + return React.cloneElement(child, childPropsMap.Field(child)); + } + if (childPropsMap.input && type === 'input') { + return React.cloneElement(child, childPropsMap.input(child)); + } if (child.props && child.props.children) { const newChildren = React.Children.map(child.props.children, nestedChild => ( - mapPropsToChild(nestedChild, propsForChildType))); + mapPropsToChild(nestedChild, childPropsMap))); return React.cloneElement(child, childProps, newChildren); } return child; From 05dbc5d275da787bd6d0fc52e9e388f27227a92c Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 25 Feb 2017 21:56:08 -0600 Subject: [PATCH 04/15] Implemented a makePropsForStatus function used to pass the appropriate validity and pristine state info down --- dist/components/Form.js | 9 ++++++++- dist/helpers/utilities.js | 13 +++++++++++++ src/components/Form.jsx | 12 ++++++++++-- src/helpers/utilities.jsx | 9 +++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/dist/components/Form.js b/dist/components/Form.js index 670b226..bec9c82 100644 --- a/dist/components/Form.js +++ b/dist/components/Form.js @@ -81,7 +81,14 @@ var Form = function (_React$Component) { _react2.default.Children.map(this.props.children, function (child) { return (0, _utilities.mapPropsToChild)(child, { Field: function Field(grandChild) { return (0, _utilities.makeFieldProps)(grandChild, _this3.onFieldChange, _this3.state); - } }); + }, + pristine: function pristine() { + return (0, _utilities.makePropsForStatus)('pristine', _this3.state); + }, + valid: function valid() { + return (0, _utilities.makePropsForStatus)('valid', _this3.state); + } + }); }) ); } diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index a664f66..99ed797 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + exports.assembleValidators = assembleValidators; exports.updateValidators = updateValidators; exports.isValid = isValid; @@ -10,6 +13,7 @@ exports.buildStateForField = buildStateForField; exports.addFieldsToState = addFieldsToState; exports.getValuesOf = getValuesOf; exports.makeFieldProps = makeFieldProps; +exports.makePropsForStatus = makePropsForStatus; exports.mapPropsToChild = mapPropsToChild; var _react = require('react'); @@ -124,6 +128,15 @@ function makeFieldProps(child, onChange, state) { return null; } +function makePropsForStatus(status, state) { + return Object.keys(state).reduce(function (props, field) { + if (Object.prototype.hasOwnProperty.call(state.field, status)) { + return _extends({}, props, _defineProperty({}, field, state[field][status])); + } + return props; + }, {}); +} + function mapPropsToChild(child, childPropsMap) { var type = typeof child.type === 'function' ? child.type.name : child.type; var childProps = {}; diff --git a/src/components/Form.jsx b/src/components/Form.jsx index 54c5e44..1748c90 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -1,5 +1,10 @@ import React from 'react'; -import { addFieldsToState, mapPropsToChild, makeFieldProps } from '../helpers/utilities'; +import { + addFieldsToState, + mapPropsToChild, + makeFieldProps, + makePropsForStatus, +} from '../helpers/utilities'; const Form = class extends React.Component { constructor(props) { @@ -37,7 +42,10 @@ const Form = class extends React.Component { .map(this.props.children, child => mapPropsToChild( child, - { Field: grandChild => makeFieldProps(grandChild, this.onFieldChange, this.state) }, + { Field: grandChild => makeFieldProps(grandChild, this.onFieldChange, this.state), + pristine: () => makePropsForStatus('pristine', this.state), + valid: () => makePropsForStatus('valid', this.state), + }, ), )} diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index 6905969..ea1d4ce 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -75,6 +75,15 @@ export function makeFieldProps(child, onChange, state) { return null; } +export function makePropsForStatus(status, state) { + return Object.keys(state).reduce((props, field) => { + if (Object.prototype.hasOwnProperty.call(state.field, status)) { + return { ...props, [field]: state[field][status] }; + } + return props; + }, {}); +} + export function mapPropsToChild(child, childPropsMap) { const type = (typeof child.type === 'function') ? child.type.name : child.type; const childProps = {}; From 6c7a300fe75d510857cc90f82f4f48dcf1905027 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 25 Feb 2017 23:14:55 -0600 Subject: [PATCH 05/15] Modified initial form validity to run validators, rather than defaulting to false --- dist/helpers/utilities.js | 46 +++++++++++++++++++---------------- src/helpers/utilities.jsx | 31 ++++++++++++----------- tests/components/Form.spec.js | 45 ++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 35 deletions(-) diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index 99ed797..5a4b2fd 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -9,9 +9,9 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument exports.assembleValidators = assembleValidators; exports.updateValidators = updateValidators; exports.isValid = isValid; +exports.getValuesOf = getValuesOf; exports.buildStateForField = buildStateForField; exports.addFieldsToState = addFieldsToState; -exports.getValuesOf = getValuesOf; exports.makeFieldProps = makeFieldProps; exports.makePropsForStatus = makePropsForStatus; exports.mapPropsToChild = mapPropsToChild; @@ -81,16 +81,27 @@ function isValid(value, validators) { }, true); } +function getValuesOf() { + var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + return Object.keys(obj).map(function (key) { + return obj[key]; + }); +} + function buildStateForField(fieldProps) { - var value = fieldProps.value, - valid = fieldProps.valid, - pristine = fieldProps.pristine; + var value = fieldProps.value; - var newState = { value: '', valid: false, pristine: true }; + console.log('fieldProps:', fieldProps); + console.log('validators:', getValuesOf(assembleValidators(fieldProps))); + console.log('valid:', isValid(value, getValuesOf(assembleValidators(fieldProps)))); + var newState = { + value: '', + valid: isValid(value, getValuesOf(assembleValidators(fieldProps))), + pristine: true + }; if (value !== undefined) Object.assign(newState, { value: value }); - if (valid !== undefined) Object.assign(newState, { valid: valid }); - if (pristine !== undefined) Object.assign(newState, { pristine: pristine }); return newState; } @@ -112,14 +123,6 @@ function addFieldsToState(component, child) { } } -function getValuesOf() { - var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - return Object.keys(obj).map(function (key) { - return obj[key]; - }); -} - function makeFieldProps(child, onChange, state) { if (typeof child.type === 'function' && child.type.name === 'Field') { var name = child.props.name; @@ -130,8 +133,8 @@ function makeFieldProps(child, onChange, state) { function makePropsForStatus(status, state) { return Object.keys(state).reduce(function (props, field) { - if (Object.prototype.hasOwnProperty.call(state.field, status)) { - return _extends({}, props, _defineProperty({}, field, state[field][status])); + if (Object.prototype.hasOwnProperty.call(state[field], status)) { + return _extends({}, props, _defineProperty({}, field + '_' + status, state[field][status])); } return props; }, {}); @@ -142,16 +145,17 @@ function mapPropsToChild(child, childPropsMap) { var childProps = {}; if (childPropsMap.valid && child.props.valid) { - Object.assign(childProps, childPropsMap.valid); + Object.assign(childProps, childPropsMap.valid()); } if (childPropsMap.pristine && child.props.pristine) { - Object.assign(childProps, childPropsMap.pristine); + Object.assign(childProps, childPropsMap.pristine()); } + if (childPropsMap.Field && type === 'Field') { - return _react2.default.cloneElement(child, childPropsMap.Field(child)); + return _react2.default.cloneElement(child, Object.assign(childPropsMap.Field(child), childProps)); } if (childPropsMap.input && type === 'input') { - return _react2.default.cloneElement(child, childPropsMap.input(child)); + return _react2.default.cloneElement(child, Object.assign(childPropsMap.input(child), childProps)); } if (child.props && child.props.children) { var newChildren = _react2.default.Children.map(child.props.children, function (nestedChild) { diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index ea1d4ce..d5b9d46 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -36,13 +36,19 @@ export function isValid(value, validators) { }, true); } +export function getValuesOf(obj = {}) { + return Object.keys(obj).map(key => obj[key]); +} + export function buildStateForField(fieldProps) { - const { value, valid, pristine } = fieldProps; - const newState = { value: '', valid: false, pristine: true }; + const { value } = fieldProps; + const newState = { + value: '', + valid: isValid(value, getValuesOf(assembleValidators(fieldProps))), + pristine: true, + }; if (value !== undefined) Object.assign(newState, { value }); - if (valid !== undefined) Object.assign(newState, { valid }); - if (pristine !== undefined) Object.assign(newState, { pristine }); return newState; } @@ -63,10 +69,6 @@ export function addFieldsToState(component, child, mounted = false) { } } -export function getValuesOf(obj = {}) { - return Object.keys(obj).map(key => obj[key]); -} - export function makeFieldProps(child, onChange, state) { if (typeof child.type === 'function' && child.type.name === 'Field') { const name = child.props.name; @@ -77,8 +79,8 @@ export function makeFieldProps(child, onChange, state) { export function makePropsForStatus(status, state) { return Object.keys(state).reduce((props, field) => { - if (Object.prototype.hasOwnProperty.call(state.field, status)) { - return { ...props, [field]: state[field][status] }; + if (Object.prototype.hasOwnProperty.call(state[field], status)) { + return { ...props, [`${field}_${status}`]: state[field][status] }; } return props; }, {}); @@ -89,16 +91,17 @@ export function mapPropsToChild(child, childPropsMap) { const childProps = {}; if (childPropsMap.valid && child.props.valid) { - Object.assign(childProps, childPropsMap.valid); + Object.assign(childProps, childPropsMap.valid()); } if (childPropsMap.pristine && child.props.pristine) { - Object.assign(childProps, childPropsMap.pristine); + Object.assign(childProps, childPropsMap.pristine()); } + if (childPropsMap.Field && type === 'Field') { - return React.cloneElement(child, childPropsMap.Field(child)); + return React.cloneElement(child, Object.assign(childPropsMap.Field(child), childProps)); } if (childPropsMap.input && type === 'input') { - return React.cloneElement(child, childPropsMap.input(child)); + return React.cloneElement(child, Object.assign(childPropsMap.input(child), childProps)); } if (child.props && child.props.children) { const newChildren = React.Children.map(child.props.children, nestedChild => ( diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index 206fa3f..c601612 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -144,5 +144,50 @@ describe('
Higher-Order-Component', () => { Form.prototype.reset.restore(); }); + + it('passes validity information down to components with a `valid` prop', () => { + wrapper = mount( + + + + , + ); + + expect(wrapper.find(Field).first().props()).to.have.property('name_valid', true); + expect(wrapper.find(Field).first().props()).to.have.property('email_valid', false); + expect(wrapper.find(Field).last().props()).to.not.have.property('name_valid'); + expect(wrapper.find(Field).last().props()).to.not.have.property('email_valid'); + }); + + it('passes pristine information down to components with a `pristine` prop', () => { + wrapper = mount( +
+ + , + ); + + expect(wrapper.find(Field).first().props()).to.have.property('name_pristine', true); + + updateInput(wrapper, 'secondValue'); + expect(wrapper.find(Field).first().props()).to.have.property('name_pristine', false); + }); + + it( + 'passes both valid and pristine information down to components with both `valid` and `pristine` props', + () => { + wrapper = mount( +
+ + , + ); + + expect(wrapper.find(Field).first().props()).to.have.property('name_pristine', true); + expect(wrapper.find(Field).first().props()).to.have.property('name_valid', false); + + updateInput(wrapper, 'firstValue'); + expect(wrapper.find(Field).first().props()).to.have.property('name_pristine', false); + expect(wrapper.find(Field).first().props()).to.have.property('name_valid', true); + }, + ); }); }); From 65211bcd2eb6938d39b025c0aa19556eeb05e5be Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 25 Feb 2017 23:20:28 -0600 Subject: [PATCH 06/15] Updated tests to reflect new initial form validity checking --- dist/helpers/utilities.js | 3 --- tests/components/Form.spec.js | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index 5a4b2fd..2d86488 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -92,9 +92,6 @@ function getValuesOf() { function buildStateForField(fieldProps) { var value = fieldProps.value; - console.log('fieldProps:', fieldProps); - console.log('validators:', getValuesOf(assembleValidators(fieldProps))); - console.log('valid:', isValid(value, getValuesOf(assembleValidators(fieldProps)))); var newState = { value: '', valid: isValid(value, getValuesOf(assembleValidators(fieldProps))), diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index c601612..e5f20b5 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -121,7 +121,7 @@ describe('
Higher-Order-Component', () => { expect(formResetSpy.callCount).to.eql(0); expect(wrapper.state().name).to.eql({ value: 'firstValue', - valid: false, + valid: true, pristine: true, }); @@ -138,7 +138,7 @@ describe(' Higher-Order-Component', () => { expect(formResetSpy.callCount).to.eql(1); expect(wrapper.state().name).to.eql({ value: 'firstValue', - valid: false, + valid: true, pristine: true, }); @@ -177,14 +177,14 @@ describe(' Higher-Order-Component', () => { () => { wrapper = mount( - + , ); expect(wrapper.find(Field).first().props()).to.have.property('name_pristine', true); expect(wrapper.find(Field).first().props()).to.have.property('name_valid', false); - updateInput(wrapper, 'firstValue'); + updateInput(wrapper, 'Test Name'); expect(wrapper.find(Field).first().props()).to.have.property('name_pristine', false); expect(wrapper.find(Field).first().props()).to.have.property('name_valid', true); }, From dc53ec65097d8bace19f3260378e4775ab1d9315 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 25 Feb 2017 23:34:18 -0600 Subject: [PATCH 07/15] Updated Form test specs to cover a missed branch in addFieldToState function --- tests/components/Form.spec.js | 161 ++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 76 deletions(-) diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index e5f20b5..6fc800f 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -40,7 +40,6 @@ describe('
Higher-Order-Component', () => { expect(inputs.last().getDOMNode().value).to.equal('user@company.com'); }); - it('passes appropriate props down through `props.children`', () => { wrapper = mount(( @@ -91,103 +90,113 @@ describe(' Higher-Order-Component', () => { }); }); - it('invoked an onSubmit callback upon form submission', () => { - const onSubmitSpy = sinon.spy(); - wrapper = mount( - - - - - - ); - } -} -``` - -## Field Component - -### Description -The `Field` component is a stateful, higher-order component which wraps a given presentational input component (or creates a default one). Input elements should be nested inside of `Field` tag. Each `Field` component will maintain its child's input element's value (`state.value` {String, Number}), validity(`state.valid`{Boolean}), and pristine state (`state.pristine` {Boolean}), as well as provide an onChange handler passed down through `props.onChange`. - -The `Field` component will behave as follows with respect to its children: - - 1. If no components are nested in a `Field` component, a default label and input element will be used. - 2. Any `input` tag will be passed `name`, `type`, `value`, and `onChange` props. - 3. If only a single direct child is passed to `Field`, it will be passed all of the relevant input props. - 4. If multiple `input` tags are nested in a single `Field`, they would all share a single state (not recommended). - -*Note:* Only one input element should be nested inside of a `Field` tag (see #4 above). - -### Props -#### `props.name = name` -> @param {String} [name=''] - The name of the wrapped input component. - - The name of the wrapped input component. If no custom input component is passed in (via `props.Input`), then a label element will be created around the input and the input will be named, both with this value. - -#### `props.value = value` -> @param {String} [value=''] - The value of the wrapped input component. - - This property is used to control the value of the wrapped input component. - -#### `props.type = type` -> @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. - - The `Field` component accepts different validators as props. See the Validators section for full list of validators to pass as props. - -#### `props.onChange = onChangeHandler` -> @param {Function} onChangeHandler - A function used to update (control) the state of the input element. - - This property will be invoked on a change event in a wrapped `input` element (unless a custom `input` element is provided, then this function will be passed down to the custom component through `props.onChange`). - - *Note:* You do **not** have to write this function if the `Field` component is nested inside of a `Form` component. - -#### `props.onFocus = onFocusHandler` -> @param {Function} onFocusHandler - A function to invoke upon input focus. - - This property will be invoked on a focus event in the wrapped `input` element. - -#### `props.onBlur = onBlurHandler` -> @param {Function} onBlurHandler - A function to invoke upon input blur. - - This property will be invoked on a blur event in the wrapped `input` element. - -### Validators - -There are also a handful of different validators and properties (debounce, length, etc.) that can be attached to the field component. This is done by declaring the validators as props on the `Field` component. See below for the list of validators. - -#### `props.debounce = duration` -> @param {Number} duration - An amount to debounce `props.onChange` invocation. - - This property adds a debounce to the input element broadcasting its state change to the `Field` component. - #### `props.required = required` > @param {Boolean} required - Toggles validation for a non-empty input. From 038ec579a2e8a4b23ba3fb66599a1142a51bd140 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sun, 26 Feb 2017 01:15:54 -0600 Subject: [PATCH 13/15] Reverting readme to previous state due to c/p error from github's editor --- readme.md | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) diff --git a/readme.md b/readme.md index 54fcf71..1e72dad 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,241 @@ +react-formulize [![Build Status](https://travis-ci.org/clocasto/react-formulize.svg?branch=master)](https://travis-ci.org/clocasto/react-formulize) [![Coverage Status](https://coveralls.io/repos/github/clocasto/react-formulize/badge.svg?branch=master&version=1_0_0)](https://coveralls.io/github/clocasto/react-formulize?branch=master&version=1_0_0) +========= + +React-formulize is a simple form validation library for React.js which wires up custom, controlled inputs through a declarative API. The library strives to be minimal, and as such, does most component communication implicity. The end result is a legible form which clearly states the rules of its behavior. + +## Table of Contents + 1. [Installation](#installation) + 2. [Usage](#usage) + 3. [`Form` Component](#form-component) + 4. [`Field` Component](#field-component) + 6. [Tests](#tests) + 7. [Contributing](#contributing) + 8. [License](#license) + 9. [Release History](#release-history) + +## Installation + + ```javascript + npm install react-formulize --save + ``` + +## Usage + +React-formulize can be used to both quickly compose forms or add validation to existing input components. + +#### Rules to follow: + 1. A `Form` component can wrap (nested JSX) a set of `Field` components or `input` elements (or fragments containing them) and automatically manage the state of them. All `Field`s and `input`s *must* have `name` props assigned to them. + 2. A `Field` component can wrap (nested JSX) an `input` element (or a fragment containing an `input`) and control its underlying state automatically. + 3. Pass validator props to the `Field` components. A `Field` component will keep track of its own validity. + 4. Pass an `onSubmit` handler to `Form` in order to interact with the submission event. The callback will be passed a clone of the `Form`'s state. + +#### Example: Composing A New Form With Custom Input Component(s) +```javascript + import React from 'react'; + import { Form, Field } from 'react-formulize'; + import { AgePickerComponent } from './components/agePicker'; + + const onSubmit = formState => console.log(formState); + + export default function (props) { + return ( +
+ + + + + + + Email Address must use a '.edu' domain! + + + + + ); + } +} +``` + +## Field Component + +### Description +The `Field` component is a stateful, higher-order component which wraps a given presentational input component (or creates a default one). Input elements should be nested inside of `Field` tag. Each `Field` component will maintain its child's input element's value (`state.value` {String, Number}), validity(`state.valid`{Boolean}), and pristine state (`state.pristine` {Boolean}), as well as provide an onChange handler passed down through `props.onChange`. + +The `Field` component will behave as follows with respect to its children: + + 1. If no components are nested in a `Field` component, a default label and input element will be used. + 2. Any `input` tag will be passed `name`, `type`, `value`, and `onChange` props. + 3. If only a single direct child is passed to `Field`, it will be passed all of the relevant input props. + 4. If multiple `input` tags are nested in a single `Field`, they would all share a single state (not recommended). + +*Note:* Only one input element should be nested inside of a `Field` tag (see #4 above). + +### Props +#### `props.name = name` +> @param {String} [name=''] - The name of the wrapped input component. + + The name of the wrapped input component. If no custom input component is passed in (via `props.Input`), then a label element will be created around the input and the input will be named, both with this value. + +#### `props.value = value` +> @param {String} [value=''] - The value of the wrapped input component. + + This property is used to control the value of the wrapped input component. + +#### `props.type = type` +> @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. + + The `Field` component accepts different validators as props. See the Validators section for full list of validators to pass as props. + +#### `props.onChange = onChangeHandler` +> @param {Function} onChangeHandler - A function used to update (control) the state of the input element. + + This property will be invoked on a change event in a wrapped `input` element (unless a custom `input` element is provided, then this function will be passed down to the custom component through `props.onChange`). + + *Note:* You do **not** have to write this function if the `Field` component is nested inside of a `Form` component. + +#### `props.onFocus = onFocusHandler` +> @param {Function} onFocusHandler - A function to invoke upon input focus. + + This property will be invoked on a focus event in the wrapped `input` element. + +#### `props.onBlur = onBlurHandler` +> @param {Function} onBlurHandler - A function to invoke upon input blur. + + This property will be invoked on a blur event in the wrapped `input` element. + +### Validators + +There are also a handful of different validators and properties (debounce, length, etc.) that can be attached to the field component. This is done by declaring the validators as props on the `Field` component. See below for the list of validators. + +#### `props.debounce = duration` +> @param {Number} duration - An amount to debounce `props.onChange` invocation. + + This property adds a debounce to the input element broadcasting its state change to the `Field` component. + #### `props.required = required` > @param {Boolean} required - Toggles validation for a non-empty input. From 501a24939d3c00ed2ee1bd28fe30c2d64d3ab3eb Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sun, 26 Feb 2017 01:21:14 -0600 Subject: [PATCH 14/15] Updating the readme to reflect the changes in this branch --- readme.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 1e72dad..970e0ea 100644 --- a/readme.md +++ b/readme.md @@ -28,6 +28,7 @@ React-formulize can be used to both quickly compose forms or add validation to e 2. A `Field` component can wrap (nested JSX) an `input` element (or a fragment containing an `input`) and control its underlying state automatically. 3. Pass validator props to the `Field` components. A `Field` component will keep track of its own validity. 4. Pass an `onSubmit` handler to `Form` in order to interact with the submission event. The callback will be passed a clone of the `Form`'s state. + 5. Pass `valid` and `pristine` props to any nested child components in either a `Form` or `Field` component. These components will receive information about the `Form`'s status in the format of `${fieldName}_${statusType}` (e.g. name_valid & email_pristine). #### Example: Composing A New Form With Custom Input Component(s) ```javascript @@ -112,8 +113,10 @@ The `Form` component is a stateful higher-order-component which wraps presentati The `Form` component will behave as follows with respect to its children: 1. Any `Field` tag will be passed the state associated with the `Field`'s name (`Form.state[child.props.name]`). - 2. Any other component or element will be rendered with the props it would otherwise be passed. - 3. Upon submission, `Form` will pass its `onSubmit` callback a clone of its current state. + 2. Any component with a `valid` prop will be passed props stating the validity for all `Field`s in the `Form` (e.g. name_valid). + 3. Any component with a `pristine` prop will be passed a props stating the pristine state for all `Field`s in the `Form` (e.g. email_pristine). + 4. Any other component or element will be rendered with the props it would otherwise be passed. + 5. Upon submission, `Form` will pass its `onSubmit` callback a clone of its current state. *Note:* The `Form` component should be passed an `onSubmit` handler if you want to interact with the submission event! @@ -184,7 +187,9 @@ The `Field` component will behave as follows with respect to its children: 2. Any `input` tag will be passed `name`, `type`, `value`, and `onChange` props. 3. If only a single direct child is passed to `Field`, it will be passed all of the relevant input props. 4. If multiple `input` tags are nested in a single `Field`, they would all share a single state (not recommended). - + 5. Any component with a `valid` prop will be passed a prop stating the `Field`'s validity (e.g. name_valid). + 6. Any component with a `pristine` prop will be passed a prop stating the `Field`'s pristine state (e.g. email_pristine). + *Note:* Only one input element should be nested inside of a `Field` tag (see #4 above). ### Props From 64153122d6acd139225ecd4220c3ac31855499bf Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sun, 26 Feb 2017 11:36:40 -0600 Subject: [PATCH 15/15] Updated feature-#30 branch test spec to reflect new initial field states in 'Form' --- tests/components/Form.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index 4663795..2863e03 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -247,7 +247,7 @@ describe('
Higher-Order-Component', () => { expect(fieldComponent.props()).to.have.property('name', 'nameField'); expect(fieldComponent.props()).to.have.property('value', 'firstValue'); expect(fieldComponent.props()).to.have.property('passedValue', 'secondValue'); - expect(wrapper.state().nameField).to.eql({ value: 'firstValue', valid: false, pristine: true }); + expect(wrapper.state().nameField).to.eql({ value: 'firstValue', valid: true, pristine: true }); }); }); });