From 2c3cf1854e39d14b5536f8d20e812d6e5c60bd9d Mon Sep 17 00:00:00 2001 From: Ray Knight Date: Tue, 23 Apr 2019 16:07:56 -0700 Subject: [PATCH 1/3] feat: add value transforms add value transforms to allow data manipulation before validation --- README.md | 34 +++++++++++++++++++++++++ src/Form.js | 26 ++++++++++++++----- src/FormValidation.js | 8 ++++-- src/Validation.js | 23 ++++++++++++----- src/__tests__/integration-tests.spec.js | 32 +++++++++++++++++++++++ 5 files changed, 108 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2eff87e..4b3e4c1 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,21 @@ The `initialValues` object lets you specify the initial values of the form fields. These values are available from the `fields` argument in the `children` function, which lets you control your form fields. +##### `valueTransforms: object` + +The `valueTransforms` object lets you apply transforms to the value before it +is stored and validated against. Each key should map to a field name and be a function +that receives and returns a value. This is useful if you wish to convert a value's +type or enforce casing. + +```js +{ + foo: (value) => parseInt(value), + bar: (value) => !!value ? 'YES' : 'NO', + etc: (value) => value.toLowerCase(), +} +``` + ##### `onSubmit: func` This callback is fired whenever the form is submitted. That can happen whenever @@ -522,6 +537,25 @@ Each validator can accept an object with a `message` key or - in the case where you don't have to specify anything other than a validation message - just a string with the error message. +##### `initialValues: object` + +The `initialValues` object lets you specify the initial values of the form +fields. These values are available from the `fields` argument in the `children` +function, which lets you control your form fields. + +The `valueTransforms` object lets you apply transforms to the value before it +is stored and validated against. Each key should map to a field name and be a function +that receives and returns a value. This is useful if you wish to convert a value's +type or enforce casing. + +```js +{ + foo: (value) => parseInt(value), + bar: (value) => !!value ? 'YES' : 'NO', + etc: (value) => value.toLowerCase(), +} +``` + ### `ValidatorsProvider` `import { ValidatorsProvider } from 'calidators';` diff --git a/src/Form.js b/src/Form.js index 568305c..200b2e8 100755 --- a/src/Form.js +++ b/src/Form.js @@ -33,19 +33,24 @@ class Form extends Component { }; initialValues = {}; + transforms = {}; onChange = e => { this.props.onChange(e); - if (!this.state.config[e.target.name]) { + const { checked, name, type, value } = e.target; + + if (!this.state.config[name]) { return; } + const transform = + typeof this.transforms[name] === 'function' + ? this.transforms[name] + : value => value; + this.setField({ - [e.target.name]: - e.target.type === 'checkbox' - ? e.target.checked - : e.target.value, + [name]: transform(type === 'checkbox' ? checked : value), }); }; @@ -177,11 +182,19 @@ class Form extends Component { null, ); - registerSubComponent = (subComponentConfig, initialValues) => { + registerSubComponent = ( + subComponentConfig, + valueTransforms, + initialValues, + ) => { this.initialValues = { ...this.initialValues, ...initialValues, }; + this.transforms = { + ...this.transforms, + ...valueTransforms, + }; this.setState(prevState => { const config = { @@ -205,6 +218,7 @@ class Form extends Component { const keys = Object.keys(subComponentConfig); this.initialValues = removeFrom(this.initialValues)(keys); + this.transforms = removeFrom(this.transforms)(keys); this.setState(prevState => { const config = removeFrom(prevState.config)(keys); diff --git a/src/FormValidation.js b/src/FormValidation.js index 6188a7e..c7c6959 100644 --- a/src/FormValidation.js +++ b/src/FormValidation.js @@ -4,11 +4,15 @@ import Form from './Form'; import Validation from './Validation'; const FormValidation = props => { - const { children, config, initialValues, ...rest } = props; + const { children, config, initialValues, valueTransforms, ...rest } = props; return (
- + {children} diff --git a/src/Validation.js b/src/Validation.js index 1251dd5..9084352 100755 --- a/src/Validation.js +++ b/src/Validation.js @@ -7,6 +7,7 @@ const propTypes = { children: func.isRequired, config: shape({}).isRequired, initialValues: shape({}), + valueTransforms: shape({}), }; class Validation extends Component { @@ -14,6 +15,7 @@ class Validation extends Component { errors: {}, fields: {}, initialValues: {}, + valueTransforms: {}, }; static propTypes = { @@ -37,17 +39,24 @@ class Validation extends Component { }; componentDidMount() { - const { register, initialValues, config } = this.props; + const { config, initialValues, register, valueTransforms } = this.props; register( config, - Object.keys(config).reduce( - (allFields, field) => ({ + valueTransforms, + Object.keys(config).reduce((allFields, field) => { + const transform = + typeof valueTransforms[field] === 'function' + ? valueTransforms[field] + : value => value; + + return { ...allFields, - [field]: getFirstDefinedValue(initialValues[field], ''), - }), - {}, - ), + [field]: transform( + getFirstDefinedValue(initialValues[field], ''), + ), + }; + }, {}), ); this.setState({ isRegistered: true }); diff --git a/src/__tests__/integration-tests.spec.js b/src/__tests__/integration-tests.spec.js index 99d6971..1aff14f 100644 --- a/src/__tests__/integration-tests.spec.js +++ b/src/__tests__/integration-tests.spec.js @@ -183,6 +183,38 @@ describe('', () => { expect(getByLabelText(/optional/i).value).toBe(''); expect(queryByTestId('optional-error')).toBeNull(); }); + + it('transforms values (initial & after change)', () => { + const initialValues = { username: 'foo', optional: '77' }; + const { getByLabelText, queryByTestId } = render( + value.toUpperCase(), + optional: value => (parseInt(value) < 100 ? '0' : '1'), + }} + > + {props => } + , + ); + + expect(getByLabelText(/username/i).value).toBe('FOO'); + + fireEvent.change(getByLabelText(/username/i), { + target: { name: 'username', value: 'bar' }, + }); + + expect(getByLabelText(/username/i).value).toBe('BAR'); + + expect(getByLabelText(/optional/i).value).toBe('0'); + + fireEvent.change(getByLabelText(/optional/i), { + target: { name: 'optional', value: '109' }, + }); + + expect(getByLabelText(/optional/i).value).toBe('1'); + }); }); describe('', () => { From da5f45a9abe0236a5afeaa49b2cda3441d2c889f Mon Sep 17 00:00:00 2001 From: Ray Knight Date: Wed, 24 Apr 2019 12:58:09 -0700 Subject: [PATCH 2/3] refactor(transforms/docs): rename to transforms renamed valueTransforms to transforms, removed duplicate documentation --- README.md | 73 +------------------------ src/Form.js | 21 +++---- src/FormValidation.js | 4 +- src/Validation.js | 21 ++++--- src/__tests__/integration-tests.spec.js | 2 +- src/__tests__/utilities.spec.js | 2 +- 6 files changed, 25 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 4b3e4c1..d5aacca 100644 --- a/README.md +++ b/README.md @@ -399,80 +399,13 @@ When you have a simple form to validate. #### Props -##### `children: func.isRequired` - -The `children` function is called with an object with the following props: - -```js -{ - dirty: object, // Object with all fields isDirty state, keyed per field - errors: object, // object with the same keys as `fields`, but with error messages - fields: object, // object with the form field values, to make controlled components - resetAll: func, // call this to programmatically trigger a full state reset - setError: func, // callback accepting a diff object, updating errors like setState - setField: func, // callback accepting a diff object, updating fields like setState - submit: func, // call this to programmatically trigger a submitted state - submitted: bool, // flag showing whether the form has been submitted once or not -} -``` - -The `setField` function is used whenever you want to update a field outside of -a typical `change` event. Pass an object with the diff you want to apply (like -React's `setState`), and it will update and reevaluate your form. - -##### `config: object.isRequired` - -The config object specifies what you want to validate, and which validators to -apply to it. - -Each validator can accept an object with a `message` key or - in the case where -you don't have to specify anything other than a validation message - just a -string with the error message. - -##### `initialValues: object` - -The `initialValues` object lets you specify the initial values of the form -fields. These values are available from the `fields` argument in the `children` -function, which lets you control your form fields. - -##### `valueTransforms: object` - -The `valueTransforms` object lets you apply transforms to the value before it -is stored and validated against. Each key should map to a field name and be a function -that receives and returns a value. This is useful if you wish to convert a value's -type or enforce casing. - -```js -{ - foo: (value) => parseInt(value), - bar: (value) => !!value ? 'YES' : 'NO', - etc: (value) => value.toLowerCase(), -} -``` - -##### `onSubmit: func` - -This callback is fired whenever the form is submitted. That can happen whenever -somebody clicks the submit button, or hits `enter` in the form. - -The `onSubmit` function is called with an object with the following props: - -```js -{ - dirty: object, // Object with all fields isDirty state, keyed per field - errors: object, // Object with all error messages, keyed per field - fields: object, // Object with all field inputs, keyed per field - isValid: bool, // Boolean indicating whether your form is valid or not - resetAll: func, // call this to programmatically trigger a full state reset - setError: func, // callback accepting a diff object, updating errors like setState -} -``` +Accepts all of the props from `Form` and `Validation below. ### `Form` `import { Form } from 'calidators';` -When you want to wrap a complex form (in conjunction ) +When you want to wrap a complex form (in conjunction) #### Props @@ -543,7 +476,7 @@ The `initialValues` object lets you specify the initial values of the form fields. These values are available from the `fields` argument in the `children` function, which lets you control your form fields. -The `valueTransforms` object lets you apply transforms to the value before it +The `transforms` object lets you apply transforms to the value before it is stored and validated against. Each key should map to a field name and be a function that receives and returns a value. This is useful if you wish to convert a value's type or enforce casing. diff --git a/src/Form.js b/src/Form.js index 200b2e8..f876f4e 100755 --- a/src/Form.js +++ b/src/Form.js @@ -44,14 +44,13 @@ class Form extends Component { return; } - const transform = - typeof this.transforms[name] === 'function' - ? this.transforms[name] - : value => value; + let val = type === 'checkbox' ? checked : value; - this.setField({ - [name]: transform(type === 'checkbox' ? checked : value), - }); + if (typeof this.transforms[name] === 'function') { + val = this.transforms[name](val); + } + + this.setField({ [name]: val }); }; onReset = e => { @@ -182,18 +181,14 @@ class Form extends Component { null, ); - registerSubComponent = ( - subComponentConfig, - valueTransforms, - initialValues, - ) => { + registerSubComponent = (subComponentConfig, transforms, initialValues) => { this.initialValues = { ...this.initialValues, ...initialValues, }; this.transforms = { ...this.transforms, - ...valueTransforms, + ...transforms, }; this.setState(prevState => { diff --git a/src/FormValidation.js b/src/FormValidation.js index c7c6959..0e4d340 100644 --- a/src/FormValidation.js +++ b/src/FormValidation.js @@ -4,14 +4,14 @@ import Form from './Form'; import Validation from './Validation'; const FormValidation = props => { - const { children, config, initialValues, valueTransforms, ...rest } = props; + const { children, config, initialValues, transforms, ...rest } = props; return (
{children} diff --git a/src/Validation.js b/src/Validation.js index 9084352..636f91a 100755 --- a/src/Validation.js +++ b/src/Validation.js @@ -7,7 +7,7 @@ const propTypes = { children: func.isRequired, config: shape({}).isRequired, initialValues: shape({}), - valueTransforms: shape({}), + transforms: shape({}), }; class Validation extends Component { @@ -15,7 +15,7 @@ class Validation extends Component { errors: {}, fields: {}, initialValues: {}, - valueTransforms: {}, + transforms: {}, }; static propTypes = { @@ -39,22 +39,21 @@ class Validation extends Component { }; componentDidMount() { - const { config, initialValues, register, valueTransforms } = this.props; + const { config, initialValues, register, transforms } = this.props; register( config, - valueTransforms, + transforms, Object.keys(config).reduce((allFields, field) => { - const transform = - typeof valueTransforms[field] === 'function' - ? valueTransforms[field] - : value => value; + let value = getFirstDefinedValue(initialValues[field], ''); + + if (typeof transforms[field] === 'function') { + value = transforms[field](value); + } return { ...allFields, - [field]: transform( - getFirstDefinedValue(initialValues[field], ''), - ), + [field]: value, }; }, {}), ); diff --git a/src/__tests__/integration-tests.spec.js b/src/__tests__/integration-tests.spec.js index 1aff14f..69c7bb4 100644 --- a/src/__tests__/integration-tests.spec.js +++ b/src/__tests__/integration-tests.spec.js @@ -190,7 +190,7 @@ describe('', () => { value.toUpperCase(), optional: value => (parseInt(value) < 100 ? '0' : '1'), }} diff --git a/src/__tests__/utilities.spec.js b/src/__tests__/utilities.spec.js index f63e10c..90430e6 100644 --- a/src/__tests__/utilities.spec.js +++ b/src/__tests__/utilities.spec.js @@ -1,4 +1,4 @@ -import { areDirty, getFirstDefinedValue, removeFrom, uuid } from '../utilities'; +import { areDirty, getFirstDefinedValue, removeFrom } from '../utilities'; const foo = 'foo'; const bar = 'bar'; From a0ceb9f74d7e3dbd01986e222230ccbf3893ac03 Mon Sep 17 00:00:00 2001 From: Ray Knight Date: Wed, 24 Apr 2019 13:01:35 -0700 Subject: [PATCH 3/3] docs(typo): fixed typo fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5aacca..85e4d3d 100644 --- a/README.md +++ b/README.md @@ -399,7 +399,7 @@ When you have a simple form to validate. #### Props -Accepts all of the props from `Form` and `Validation below. +Accepts all of the props from `Form` and `Validation` below. ### `Form`