diff --git a/.gitignore b/.gitignore index 3597cb4..effd64a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ build/ # Misc .DS_Store + +.idea diff --git a/package.json b/package.json index 380aaa9..6ce4939 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "reacto-form", - "version": "0.0.0-development", + "name": "@gwhobbs/reacto-form", + "version": "0.0.4-development", "description": "A React form state manager hook designed to work with many popular form UI frameworks", "main": "./build/index.js", "types": "./build/index.d.ts", @@ -28,7 +28,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/longshotlabs/reacto-form.git" + "url": "git+https://github.com/gwhobbs/reacto-form.git" }, "keywords": [], "author": "Long Shot Labs (longshotlabs.co)", @@ -88,7 +88,7 @@ "typescript": "^4.6.4" }, "peerDependencies": { - "react": ">=17 | >=18" + "react": "^17.0.0 || ^18.0.0" }, "dependencies": { "lodash": "^4.17.21", diff --git a/src/types.ts b/src/types.ts index ef11193..675799c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,6 +19,7 @@ export interface GetInputPropsOptions { nullValue?: any onChangeGetValue?: (...args: any[]) => any onChangingGetValue?: (...args: any[]) => any + onApplyChangeToForm?: (formData: FormData, fieldValue: any, fieldPath: string) => FormData, propNames?: Partial } @@ -57,6 +58,7 @@ export interface UseReactoFormState { getErrors: (fieldPaths: string[], options?: ErrorOptions) => ValidationError[] getFirstError: (fieldPaths: string[], options?: ErrorOptions) => ValidationError | null getFirstErrorMessage: (fieldPaths: string[], options?: ErrorOptions) => string | null + updateFormData: (formData: FormData) => void getInputProps: (fieldPath: string, options?: GetInputPropsOptions) => Record hasBeenValidated: boolean hasErrors: (fieldPaths: string[], options?: ErrorOptions) => boolean diff --git a/src/useReactoForm.ts b/src/useReactoForm.ts index ad66d6f..321a049 100644 --- a/src/useReactoForm.ts +++ b/src/useReactoForm.ts @@ -188,14 +188,41 @@ export default function useReactoForm (props: UseReactoFormProps): UseReactoForm return fieldErrors[0] } + function applyValueChange (updatedFormData: FormData, isValidationRequired = false) { + // Bubble up the `onChange`, possibly validating first + if (isValidationRequired) { + validateForm() + .then((updatedErrors) => { + onChange(updatedFormData, updatedErrors.length === 0) + return null + }) + .catch((error) => { + console.error(error) + }) + } else { + onChange(updatedFormData, errors.length === 0) + } + } + const formState: UseReactoFormState = { formData, + updateFormData (formData) { + const isValidationRequired = + validateOn === 'changed' || + validateOn === 'changing' || + (hasBeenValidated && + (revalidateOn === 'changed' || revalidateOn === 'changing')) + + setFormData(formData) + applyValueChange(formData, isValidationRequired) + }, getInputProps (fieldPath, getInputPropsOptions = {}) { const { isForm = false, nullValue, onChangeGetValue, - onChangingGetValue + onChangingGetValue, + onApplyChangeToForm, } = getInputPropsOptions const propNames: InputPropNameMap = { ...DEFAULT_PROP_NAMES } @@ -226,30 +253,29 @@ export default function useReactoForm (props: UseReactoFormProps): UseReactoForm // directly as the first arg. Many popular libraries pass // an Event as the first arg, and `onChangeGetValue` can be // used to determine and return the new value. + const handleApplyChangeToForm = (formData: FormData, newValue: any, fieldPath: string) => { + if (typeof onApplyChangeToForm !== 'function') { + return formData + } + const nextFormData = onApplyChangeToForm(formData, newValue, fieldPath) + setFormData(nextFormData) + return nextFormData + } const inputValue = (onChangeGetValue != null) ? onChangeGetValue(...onChangeArgs) : onChangeArgs[0] - const updatedFormData = setFieldValueInFormData(fieldPath, inputValue) + const updatedFormData = (onApplyChangeToForm != null) + ? handleApplyChangeToForm(clone(formData), inputValue, fieldPath) + : setFieldValueInFormData(fieldPath, inputValue) - // Now bubble up the `onChange`, possibly validating first - if ( + const isValidationRequired = validateOn === 'changed' || validateOn === 'changing' || (hasBeenValidated && (revalidateOn === 'changed' || revalidateOn === 'changing')) - ) { - validateForm() - .then((updatedErrors) => { - onChange(updatedFormData, updatedErrors.length === 0) - return null - }) - .catch((error) => { - console.error(error) - }) - } else { - onChange(updatedFormData, errors.length === 0) - } + + applyValueChange(updatedFormData, isValidationRequired) } function onInputValueChanging (...onChangingArgs: any[]): void { @@ -263,21 +289,11 @@ export default function useReactoForm (props: UseReactoFormProps): UseReactoForm const updatedFormData = setFieldValueInFormData(fieldPath, inputValue) - if ( + const isValidationRequired = validateOn === 'changing' || (hasBeenValidated && revalidateOn === 'changing') - ) { - validateForm() - .then((updatedErrors) => { - onChanging(updatedFormData, updatedErrors.length === 0) - return null - }) - .catch((error) => { - console.error(error) - }) - } else { - onChanging(updatedFormData, errors.length === 0) - } + + applyValueChange(updatedFormData, isValidationRequired) } // Some input components (MUI) do not accept a `null` value.