From b33411b7c3e0cc73650364af990cebce30540618 Mon Sep 17 00:00:00 2001 From: Vitaly Kondratiev Date: Mon, 26 Mar 2018 22:24:34 +0100 Subject: [PATCH] adds Control HOC and shouldValidate prop --- examples/package-lock.json | 6 ++-- examples/package.json | 2 +- examples/src/Form.tsx | 19 +++++++------ package.json | 2 +- src/FormContainer.tsx | 50 ++++++++++++++++++++++++++++++--- src/__tests__/validate.test.tsx | 39 +++++++++++++------------ src/interfaces.ts | 1 + src/main.ts | 2 +- src/validate.ts | 7 +++++ 9 files changed, 91 insertions(+), 37 deletions(-) diff --git a/examples/package-lock.json b/examples/package-lock.json index e48453d..2adfcc6 100644 --- a/examples/package-lock.json +++ b/examples/package-lock.json @@ -3675,9 +3675,9 @@ } }, "form-container": { - "version": "0.2.4-rc11", - "resolved": "https://registry.npmjs.org/form-container/-/form-container-0.2.4-rc11.tgz", - "integrity": "sha512-N+f57VAOxbU0BqzsT5d3I6nSOrv1Mm/Jc6Ze5DozJ5he1LgXpHM1rh+xjlVEevjdA/nLY5DOumWzKNqWAz0U/g==", + "version": "0.2.5-rc1", + "resolved": "https://registry.npmjs.org/form-container/-/form-container-0.2.5-rc1.tgz", + "integrity": "sha512-EUxtOCxv5+eqaG7L8OND7ka1D0rhufiL6QXagMAcQDLaT7ok6XK1RicwjEG8Gpm/Y+T3zroFLHvFrfHuQL47fA==", "requires": { "hoist-non-react-statics": "2.5.0", "react": "16.2.0" diff --git a/examples/package.json b/examples/package.json index 2904c12..7f914f4 100644 --- a/examples/package.json +++ b/examples/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "form-container": "^0.2.4-rc11", + "form-container": "^0.2.5-rc1", "material-ui-next": "^1.0.0-beta.38", "react": "^16.2.0", "react-dom": "^16.2.0", diff --git a/examples/src/Form.tsx b/examples/src/Form.tsx index 2f38bb6..b6d314d 100644 --- a/examples/src/Form.tsx +++ b/examples/src/Form.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { connectForm, IFormProps } from 'form-container'; +import { connectForm, IFormProps, Control } from 'form-container'; import { email, required, alphaNumeric, strongPassword } from './validators'; import { TextField, Button, CardActions, CardHeader, CardContent } from 'material-ui-next'; @@ -28,14 +28,15 @@ class Form extends React.Component {
- + + + =6.0.0" }, diff --git a/src/FormContainer.tsx b/src/FormContainer.tsx index e373bb4..7a1717b 100644 --- a/src/FormContainer.tsx +++ b/src/FormContainer.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; import { pipe, isNil } from './utils'; import * as validation from './validate'; -import { IFormConfig, IBoundInput } from './interfaces'; +import { IFormConfig, IBoundInput, IFormProps } from './interfaces'; +import { ReactElement } from 'react'; const hoistNonReactStatics = require('hoist-non-react-statics'); @@ -13,7 +14,8 @@ const makeWrapper = (config: IFormConfig) => (WrappedComponent: this.state = { model: config.initialModel || {}, touched: {}, - inputs: {} + inputs: {}, + shouldValidate: {} }; } @@ -33,6 +35,12 @@ const makeWrapper = (config: IFormConfig) => (WrappedComponent: setFieldToTouched = (prop: keyof T) => this.setTouched(Object.assign({}, this.state.touched, { [prop]: true })); + setShouldValidate = (prop: keyof T, isSet: boolean = true) => { + const shouldValidate = Object.assign({}, this.state.shouldValidate, { [prop]: isSet }); + this.setState({ shouldValidate }); + return shouldValidate; + }; + getValue = (name: keyof T) => { const { state: { model: { [name]: modelValue } } } = this; @@ -97,7 +105,8 @@ const makeWrapper = (config: IFormConfig) => (WrappedComponent: form: { model: this.state.model, inputs: this.state.inputs, - touched: this.state.touched + touched: this.state.touched, + shouldValidate: this.state.shouldValidate }, formMethods: { bindInput: this.bindInput, @@ -105,7 +114,8 @@ const makeWrapper = (config: IFormConfig) => (WrappedComponent: bindToChangeEvent: this.bindToChangeEvent, setProperty: this.setProperty, setModel: this.setModel, - setFieldToTouched: this.setFieldToTouched + setFieldToTouched: this.setFieldToTouched, + setShouldValidate: this.setShouldValidate } }); @@ -122,3 +132,35 @@ export const connectForm = ( validators: any[] = [], config: IFormConfig = {} ) => (Component: any) => pipe(validation.validate(validators), makeWrapper(config))(Component); + +export interface IControlProps extends IFormProps { + name: string; + render?: (...args: any[]) => any; + shouldValidate?: boolean; +} + +export class Control extends React.Component { + componentDidMount() { + this.setShouldValidate(); + } + + componentWillReceiveProps(nextProps: IControlProps) { + if (nextProps.shouldValidate !== this.props.shouldValidate) { + this.setShouldValidate(); + } + } + + private setShouldValidate() { + const { shouldValidate, formMethods } = this.props; + if (shouldValidate !== undefined) { + formMethods.setShouldValidate(this.props.name, shouldValidate); + } + } + + render() { + const { name, formMethods: { bindInput } } = this.props; + return React.Children.map(this.props.children, child => + React.cloneElement(child as ReactElement, { ...bindInput(name) }) + ); + } +} diff --git a/src/__tests__/validate.test.tsx b/src/__tests__/validate.test.tsx index 35c2e48..1c1d938 100644 --- a/src/__tests__/validate.test.tsx +++ b/src/__tests__/validate.test.tsx @@ -9,6 +9,15 @@ const hoistNonReactStatics = require('hoist-non-react-statics'); const isRequired: Condition = value => !!value; const required = ValidationRuleFactory(isRequired, 'This field is required'); +const initialProps = { + form: { + model: { + foo: 'test' + }, + shouldValidate: {} + } +}; + describe('Validation', () => { describe('validate error validator', () => { it('should return a valid result of validationFn execution', () => { @@ -17,13 +26,7 @@ describe('Validation', () => { ); - const props = { - form: { - model: { - foo: 'test' - } - } - }; + const props = initialProps; const result = validation.validate([required('foo', 'Required field')])( MockComponent as any )(props); @@ -33,6 +36,7 @@ describe('Validation', () => { model: { foo: 'test' }, + shouldValidate: {}, validationErrors: {}, validationWarnings: {} } @@ -49,7 +53,8 @@ describe('Validation', () => { form: { model: { foo: '' - } + }, + shouldValidate: {} } }; const result = validation.validate([required('foo', 'Required field')])( @@ -64,7 +69,8 @@ describe('Validation', () => { validationErrors: { foo: 'Required field' }, - validationWarnings: {} + validationWarnings: {}, + shouldValidate: {} } }); }); @@ -77,13 +83,7 @@ describe('Validation', () => { ); - const props = { - form: { - model: { - foo: 'test' - } - } - }; + const props = initialProps; const result = validation.validate([ required('foo', 'Required field', ValidationType.Warning) ])(MockComponent as any)(props); @@ -94,7 +94,8 @@ describe('Validation', () => { foo: 'test' }, validationErrors: {}, - validationWarnings: {} + validationWarnings: {}, + shouldValidate: {} } }); }); @@ -109,7 +110,8 @@ describe('Validation', () => { form: { model: { foo: '' - } + }, + shouldValidate: {} } }; const result = validation.validate([ @@ -121,6 +123,7 @@ describe('Validation', () => { model: { foo: '' }, + shouldValidate: {}, validationErrors: {}, validationWarnings: { foo: 'Required field' diff --git a/src/interfaces.ts b/src/interfaces.ts index 3ff1bc5..af3ebd5 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -38,6 +38,7 @@ export interface IFormMethods { setProperty: (prop: keyof T, value: T[keyof T]) => any; setModel: (model: { [name in keyof T]?: any }) => any; setFieldToTouched: (prop: keyof T) => any; + setShouldValidate: (prop: keyof T, isSet: boolean) => any; } export interface IFormProps { diff --git a/src/main.ts b/src/main.ts index 65cc15f..3543def 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,3 @@ -export { connectForm } from './FormContainer'; +export { connectForm, Control } from './FormContainer'; export { IFormProps, IFormConfig, ValidationRule, ValidationType } from './interfaces'; export { ValidationRuleFactory } from './validators'; diff --git a/src/validate.ts b/src/validate.ts index a9be430..fdbc2da 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -20,6 +20,13 @@ const getValidationResult = ({ rules .filter(([rule, field, type = ValidationType.Error]) => type === validationType) .reduce((errors, [rule, field, type]) => { + const fieldKey = Object.keys(field)[0]; + const shouldValidate = allProps.form.shouldValidate[fieldKey]; + + if (shouldValidate === false) { + return errors; // skip further validation + } + const isValid = rule(model, allProps); if (isValid) {