From d66f1e5d84adc16466d3561aac70581aada12fd4 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Fri, 11 Feb 2022 18:24:42 -0500 Subject: [PATCH] feat(useFormField): Add onChange option for side-effects whenever anything calls field.setValue (#88) --- .../useFormState/useFormState.stories.mdx | 5 ++++- src/hooks/useFormState/useFormState.ts | 22 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/hooks/useFormState/useFormState.stories.mdx b/src/hooks/useFormState/useFormState.stories.mdx index c5f5270..c30742a 100644 --- a/src/hooks/useFormState/useFormState.stories.mdx +++ b/src/hooks/useFormState/useFormState.stories.mdx @@ -36,7 +36,10 @@ PatternFly text fields driven by this hook. function useFormField( initialValue: T, schema: [T] extends [Array] ? yup.ArraySchema : yup.Schema, - options?: { initialTouched?: boolean } + options: { + initialTouched?: boolean; // Start field with isTouched set to true + onChange?: (newValue: T) => void; // Called after any call to field.setValue, for side effects + } = {} ): IFormField; function useFormState( diff --git a/src/hooks/useFormState/useFormState.ts b/src/hooks/useFormState/useFormState.ts index 24a83b3..f8069ba 100644 --- a/src/hooks/useFormState/useFormState.ts +++ b/src/hooks/useFormState/useFormState.ts @@ -64,12 +64,30 @@ export interface IFormState { export const useFormField = ( initialValue: T, schema: yup.AnySchema, - options: { initialTouched?: boolean } = {} + options: { + initialTouched?: boolean; // Start field with isTouched set to true + onChange?: (newValue: T) => void; // Called after any call to field.setValue, for side effects + } = {} ): IFormField => { const [defaultValue, setDefaultValue] = React.useState(initialValue); // The value used on clear() const [cleanValue, setCleanValue] = React.useState(initialValue); // The value considered "unchanged", determines isDirty and used on revert() - const [value, setValue] = React.useState(initialValue); // The actual value in the field + const [value, baseSetValue] = React.useState(initialValue); // The actual value in the field const [isTouched, setIsTouched] = React.useState(options.initialTouched || false); + + // Replicates the behavior of a real useState dispatch function in order to also call `options.onChange` if present + const setValue: React.Dispatch> = ( + valueOrFn: T | ((prevValue: T) => T) + ) => { + baseSetValue(valueOrFn); + if (options.onChange) { + const newValue = + typeof valueOrFn === 'function' + ? (valueOrFn as (prevValue: T) => T)(value) + : (valueOrFn as T); + options.onChange(newValue); + } + }; + return { value, setValue,