import PropTypes from 'prop-types' ; import React from 'react' ; import FormsyContext from './FormsyContext' ; import { ComponentWithStaticAttributes , FormsyContextInterface , RequiredValidation , ValidationError , Validations , WrappedComponentClass , } from './interfaces' ; import * as utils from './utils' ; import { isString } from './utils' ; import { isDefaultRequiredValue } from './validationRules' ; const convertValidationsToObject = < V > ( validations ) : Validations < V > => { if ( isString ( validations ) ) { return validations . split ( /,(?![^{[]*[}\]])/g ) . reduce ( ( validationsAccumulator , validation ) => { let args = validation . split ( ':' ) ; const validateMethod = args . shift ( ) ; args = args . map ( ( arg ) => { try { return JSON . parse ( arg ) ; } catch ( e ) { return arg ; } } ) ; if ( args . length > 1 ) { throw new Error ( 'Formsy does not support multiple args on string validations. Use object format of validations instead.' ) ; } const validationsAccumulatorCopy = { ... validationsAccumulator } ; validationsAccumulatorCopy [ validateMethod ] = args . length ? args [ 0 ] : true ; return validationsAccumulatorCopy ; } , { } ) ; } return validations || { } ; } ; export const propTypes = { innerRef : PropTypes . func , name : PropTypes . string . isRequired , required : PropTypes . oneOfType ( [ PropTypes . bool , PropTypes . object , PropTypes . string ] ) , validations : PropTypes . oneOfType ( [ PropTypes . object , PropTypes . string ] ) , value : PropTypes . any } ; export interface WrapperProps < V > { innerRef ? : ( ref : React . Ref < any > ) => void ; name : string ; required ? : RequiredValidation < V > ; validationError ? : ValidationError ; validationErrors ? : { [ key ] : ValidationError ; } ; validations ? : Validations < V > ; value ? : V ; } export interface WrapperState < V > { [ key ] : unknown ; formSubmitted : boolean ; isPristine : boolean ; isRequired : boolean ; isValid : boolean ; pristineValue : V ; validationError : ValidationError [ ] ; value : V ; } export interface InjectedProps < V > { errorMessage : ValidationError ; errorMessages : ValidationError [ ] ; hasValue : boolean ; isFormDisabled : boolean ; isFormSubmitted : boolean ; isPristine : boolean ; isRequired : boolean ; isValid : boolean ; isValidValue : ( value : V ) => boolean ; ref ? : React . Ref < any > ; resetValue : ( ) => void ; setValidations : ( validations : Validations < V > , required : RequiredValidation < V > ) => void ; setValue : ( value : V , validate ? : boolean ) => void ; showError : boolean ; showRequired : boolean ; } export interface WrapperInstanceMethods < V > { getErrorMessage : ( ) => null | ValidationError ; getErrorMessages : ( ) => ValidationError [ ] ; getValue : ( ) => V ; isFormDisabled : ( ) => boolean ; isFormSubmitted : ( ) => boolean ; isValid : ( ) => boolean ; isValidValue : ( value : V ) => boolean ; setValue : ( value : V , validate ? : boolean ) => void ; } export type PassDownProps < V > = WrapperProps < V > & InjectedProps < V > ; function getDisplayName ( component ) { return component . displayName || component . name || ( utils . isString ( component ) ? component : 'Component' ) ; } export default function withFormsy < T , V > ( WrappedComponent ) { class WithFormsyWrapper extends React . Component < T & WrapperProps < V > & FormsyContextInterface , WrapperState < V > > implements WrapperInstanceMethods < V > { public validations ? : Validations < V > ; public requiredValidations ? : Validations < V > ; public static displayName = `Formsy(${ getDisplayName ( WrappedComponent ) })` ; public static propTypes : any = propTypes ; public static defaultProps : any = { innerRef : null , required : false , validationError : '' , validationErrors : { } , validations : null , value : ( WrappedComponent as ComponentWithStaticAttributes ) . defaultValue } ; public constructor ( props ) { super ( props ) ; const { runValidation , validations , required , value } = props ; this . state = { value } as any ; this . setValidations ( validations , required ) ; this . state = { formSubmitted : false , isPristine : true , pristineValue : props . value , value : props . value , ... runValidation ( this , props . value ) } ; } public componentDidMount ( ) { const { name , attachToForm } = this . props ; if ( ! name ) { throw new Error ( 'Form Input requires a name property when used' ) ; } attachToForm ( this ) ; } public shouldComponentUpdate ( nextProps , nextState ) { const { props , state } = this ; const isChanged = ( a , b ) : boolean => Object . keys ( a ) . some ( ( k ) => a [ k ] !== b [ k ] ) ; const isPropsChanged = isChanged ( props , nextProps ) ; const isStateChanged = isChanged ( state , nextState ) ; return isPropsChanged || isStateChanged ; } public componentDidUpdate ( prevProps ) { const { value , validations , required , validate } = this . props ; if ( ! utils . isSame ( value , prevProps . value ) ) { this . setValue ( value ) ; } if ( ! utils . isSame ( validations , prevProps . validations ) || ! utils . isSame ( required , prevProps . required ) ) { this . setValidations ( validations , required ) ; validate ( this ) ; } } public componentWillUnmount ( ) { const { detachFromForm } = this . props ; detachFromForm ( this ) ; } public getErrorMessage = ( ) : ValidationError | null => { const messages = this . getErrorMessages ( ) ; return messages . length ? messages [ 0 ] : null ; } ; public getErrorMessages = ( ) : ValidationError [ ] => { const { validationError } = this . state ; if ( ! this . isValid ( ) || this . showRequired ( ) ) { return validationError || [ ] ; } return [ ] ; } ; public getValue = ( ) : V => this . state . value ; public setValidations = ( validations , required ) : void => { this . validations = convertValidationsToObject ( validations ) || { } ; this . requiredValidations = required === true ? { isDefaultRequiredValue : required } : convertValidationsToObject ( required ) ; } ; public setValue = ( value , validate = true ) : void => { const { validate : validateForm } = this . props ; if ( ! validate ) { this . setState ( { value } ) ; } else { this . setState ( { value , isPristine : false } , ( ) => { validateForm ( this ) ; } ) ; } } ; public hasValue = ( ) => { const { value } = this . state ; return isDefaultRequiredValue ( value ) ; } ; public isFormDisabled = ( ) : boolean => this . props . isFormDisabled ; public isFormSubmitted = ( ) : boolean => this . state . formSubmitted ; public isPristine = ( ) : boolean => this . state . isPristine ; public isRequired = ( ) : boolean => ! ! this . props . required ; public isValid = ( ) : boolean => this . state . isValid ; public isValidValue = ( value ) => this . props . isValidValue ( this , value ) ; public resetValue = ( ) => { const { pristineValue } = this . state ; const { validate } = this . props ; this . setState ( { value : pristineValue , isPristine : true } , ( ) => { validate ( this ) ; } ) ; } ; public showError = ( ) : boolean => ! this . showRequired ( ) && ! this . isValid ( ) ; public showRequired = ( ) : boolean => this . state . isRequired ; public render ( ) { const { innerRef } = this . props ; const propsForElement = { ... this . props , errorMessage : this . getErrorMessage ( ) , errorMessages : this . getErrorMessages ( ) , hasValue : this . hasValue ( ) , isFormDisabled : this . isFormDisabled ( ) , isFormSubmitted : this . isFormSubmitted ( ) , isPristine : this . isPristine ( ) , isRequired : this . isRequired ( ) , isValid : this . isValid ( ) , isValidValue : this . isValidValue , resetValue : this . resetValue , setValidations : this . setValidations , setValue : this . setValue , showError : this . showError ( ) , showRequired : this . showRequired ( ) , value : this . getValue ( ) } ; if ( innerRef ) { propsForElement . ref = innerRef ; } return React . createElement ( WrappedComponent , propsForElement ) ; } } return ( props ) => React . createElement ( FormsyContext . Consumer , null , ( contextValue ) => { return React . createElement ( WithFormsyWrapper , { ... props , ... contextValue } ) ; } ) ; }