feat: remembers conditional field values after removing / readding

This commit is contained in:
James
2021-04-02 11:15:28 -04:00
parent cca71a5793
commit 988d0a4b08
23 changed files with 105 additions and 26 deletions

View File

@@ -1,10 +1,22 @@
import { Field as FieldSchema } from '../../../../fields/config/types';
import { Fields, Field, Data } from './types';
const buildValidationPromise = async (fieldState: Field, field: FieldSchema) => {
const buildValidationPromise = async (fieldState: Field, field: FieldSchema, fullData: Data = {}, data: Data = {}) => {
const validatedFieldState = fieldState;
const validationResult = typeof field.validate === 'function' ? await field.validate(fieldState.value, field) : true;
let passesConditionalLogic = true;
if (field?.admin?.condition) {
passesConditionalLogic = await field.admin.condition(fullData, data);
}
let validationResult: boolean | string = true;
if (!passesConditionalLogic) {
validationResult = true;
} else if (typeof field.validate === 'function') {
validationResult = await field.validate(fieldState.value, field);
}
if (typeof validationResult === 'string') {
validatedFieldState.errorMessage = validationResult;
@@ -26,9 +38,10 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
initialValue: value,
valid: true,
validate: field.validate,
condition: field?.admin?.condition,
};
validationPromises.push(buildValidationPromise(fieldState, field));
validationPromises.push(buildValidationPromise(fieldState, field, fullData, data));
return fieldState;
};

View File

@@ -67,11 +67,27 @@ const Form: React.FC<Props> = (props) => {
const validatedFieldState = {};
let isValid = true;
const validationPromises = Object.entries(contextRef.current.fields).map(async ([path, field]) => {
const validatedField = { ...field };
const data = contextRef.current.getData();
validatedField.valid = true;
const validationResult = typeof field.validate === 'function' ? await field.validate(field.value) : true;
const validationPromises = Object.entries(contextRef.current.fields).map(async ([path, field]) => {
const validatedField = {
...field,
valid: true,
};
const siblingData = contextRef.current.getSiblingData(path);
let passesConditionalLogic = true;
if (typeof field?.condition === 'function') {
passesConditionalLogic = await field.condition(data, siblingData);
}
let validationResult: boolean | string = true;
if (passesConditionalLogic && typeof field.validate === 'function') {
validationResult = await field.validate(field.value);
}
if (typeof validationResult === 'string') {
validatedField.errorMessage = validationResult;

View File

@@ -1,3 +1,5 @@
import { Condition } from '../../../../fields/config/types';
export type Field = {
value: unknown
initialValue: unknown
@@ -7,6 +9,7 @@ export type Field = {
disableFormData?: boolean
ignoreWhileFlattening?: boolean
stringify?: boolean
condition?: Condition
}
export type Fields = {

View File

@@ -35,6 +35,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
permissions,
admin: {
readOnly,
condition,
},
} = props;
@@ -62,6 +63,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
validate: memoizedValidate,
disableFormData,
ignoreWhileFlattening: true,
condition,
});
const addRow = useCallback(async (rowIndex) => {

View File

@@ -41,6 +41,7 @@ const Blocks: React.FC<Props> = (props) => {
permissions,
admin: {
readOnly,
condition,
},
} = props;
@@ -72,6 +73,7 @@ const Blocks: React.FC<Props> = (props) => {
validate: memoizedValidate,
disableFormData,
ignoreWhileFlattening: true,
condition,
});
const addRow = useCallback(async (rowIndex, blockType) => {

View File

@@ -23,6 +23,7 @@ const Checkbox: React.FC<Props> = (props) => {
readOnly,
style,
width,
condition,
} = {},
} = props;
@@ -42,6 +43,7 @@ const Checkbox: React.FC<Props> = (props) => {
path,
validate: memoizedValidate,
disableFormData,
condition,
});
return (

View File

@@ -23,6 +23,7 @@ const Code: React.FC<Props> = (props) => {
style,
width,
language,
condition,
} = {},
label,
minLength,
@@ -53,6 +54,7 @@ const Code: React.FC<Props> = (props) => {
path,
validate: memoizedValidate,
enableDebouncedValue: true,
condition,
});
const classes = [

View File

@@ -25,6 +25,7 @@ const DateTime: React.FC<Props> = (props) => {
style,
width,
date,
condition,
} = {},
} = props;
@@ -43,6 +44,7 @@ const DateTime: React.FC<Props> = (props) => {
} = useFieldType({
path,
validate: memoizedValidate,
condition,
});
const classes = [

View File

@@ -20,6 +20,7 @@ const Email: React.FC<Props> = (props) => {
width,
placeholder,
autoComplete,
condition,
} = {},
label,
} = props;
@@ -35,6 +36,7 @@ const Email: React.FC<Props> = (props) => {
path,
validate: memoizedValidate,
enableDebouncedValue: true,
condition,
});
const {

View File

@@ -23,6 +23,7 @@ const NumberField: React.FC<Props> = (props) => {
width,
step,
placeholder,
condition,
} = {},
} = props;
@@ -42,6 +43,7 @@ const NumberField: React.FC<Props> = (props) => {
path,
validate: memoizedValidate,
enableDebouncedValue: true,
condition,
});
const handleChange = useCallback((e) => {

View File

@@ -25,6 +25,7 @@ const RadioGroup: React.FC<Props> = (props) => {
layout = 'horizontal',
style,
width,
condition,
} = {},
options,
} = props;
@@ -44,6 +45,7 @@ const RadioGroup: React.FC<Props> = (props) => {
} = useFieldType({
path,
validate: memoizedValidate,
condition,
});
const classes = [

View File

@@ -34,6 +34,7 @@ const Relationship: React.FC<Props> = (props) => {
readOnly,
style,
width,
condition,
} = {},
} = props;
@@ -70,6 +71,7 @@ const Relationship: React.FC<Props> = (props) => {
} = useFieldType({
path: path || name,
validate: memoizedValidate,
condition,
});
const addOptions = useCallback((data, relation) => {

View File

@@ -41,6 +41,7 @@ const RichText: React.FC<Props> = (props) => {
style,
width,
placeholder,
condition,
} = {},
} = props;
@@ -101,6 +102,7 @@ const RichText: React.FC<Props> = (props) => {
path,
validate: memoizedValidate,
stringify: true,
condition,
});
const {

View File

@@ -36,6 +36,7 @@ const Select: React.FC<Props> = (props) => {
readOnly,
style,
width,
condition,
} = {},
} = props;
@@ -56,6 +57,7 @@ const Select: React.FC<Props> = (props) => {
} = useFieldType({
path,
validate: memoizedValidate,
condition,
});
const classes = [

View File

@@ -20,6 +20,7 @@ const Text: React.FC<Props> = (props) => {
readOnly,
style,
width,
condition,
} = {},
} = props;
@@ -28,6 +29,7 @@ const Text: React.FC<Props> = (props) => {
const fieldType = useFieldType<string>({
path,
validate,
condition,
enableDebouncedValue: true,
});

View File

@@ -20,6 +20,7 @@ const Textarea: React.FC<Props> = (props) => {
width,
placeholder,
rows,
condition,
} = {},
label,
minLength,
@@ -42,6 +43,7 @@ const Textarea: React.FC<Props> = (props) => {
path,
validate: memoizedValidate,
enableDebouncedValue: true,
condition,
});
const classes = [

View File

@@ -30,6 +30,7 @@ const Upload: React.FC<Props> = (props) => {
readOnly,
style,
width,
condition,
} = {},
label,
validate = upload,
@@ -51,6 +52,7 @@ const Upload: React.FC<Props> = (props) => {
const fieldType = useFieldType({
path,
validate: memoizedValidate,
condition,
});
const {

View File

@@ -15,6 +15,7 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
disableFormData,
ignoreWhileFlattening,
stringify,
condition,
} = options;
const formContext = useForm();
@@ -46,14 +47,15 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
const sendField = useCallback(async (valueToSend) => {
const fieldToDispatch = {
path,
stringify,
disableFormData,
ignoreWhileFlattening,
initialValue,
validate,
condition,
value: valueToSend,
valid: false,
errorMessage: undefined,
stringify: false,
disableFormData: false,
ignoreWhileFlattening: false,
initialValue: undefined,
validate: undefined,
};
const validationResult = typeof validate === 'function' ? await validate(valueToSend) : true;
@@ -65,14 +67,8 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
fieldToDispatch.valid = validationResult;
}
fieldToDispatch.stringify = stringify;
fieldToDispatch.disableFormData = disableFormData;
fieldToDispatch.ignoreWhileFlattening = ignoreWhileFlattening;
fieldToDispatch.initialValue = initialValue;
fieldToDispatch.validate = validate;
dispatchFields(fieldToDispatch);
}, [path, dispatchFields, validate, disableFormData, ignoreWhileFlattening, initialValue, stringify]);
}, [path, dispatchFields, validate, disableFormData, ignoreWhileFlattening, initialValue, stringify, condition]);
// Method to return from `useFieldType`, used to
// update internal field values from field component(s)

View File

@@ -1,4 +1,4 @@
import { Validate } from '../../../../fields/config/types';
import { Validate, Condition } from '../../../../fields/config/types';
export type Options = {
path: string
@@ -7,6 +7,7 @@ export type Options = {
disableFormData?: boolean
ignoreWhileFlattening?: boolean
stringify?: boolean
condition?: Condition
}
export type FieldType<T> = {

View File

@@ -36,13 +36,15 @@ const withCondition = <P extends Record<string, unknown>>(Field: React.Component
const field = getField(path);
const siblingData = getSiblingData(path);
const passesCondition = condition ? condition(data, siblingData) : true;
const fieldExists = Boolean(field);
useEffect(() => {
if (!passesCondition && fieldExists) {
dispatchFields({ type: 'REMOVE', path });
if (!passesCondition) {
dispatchFields({
...field,
valid: true,
});
}
}, [dispatchFields, passesCondition, path, fieldExists]);
}, [passesCondition, field, dispatchFields]);
if (passesCondition) {
return <Field {...props} />;

View File

@@ -22,13 +22,15 @@ export type FieldAccess = (args: {
siblingData: Record<string, unknown>
}) => Promise<boolean> | boolean;
export type Condition = (data: Record<string, unknown>, siblingData: Record<string, unknown>) => boolean
type Admin = {
position?: string;
width?: string;
style?: CSSProperties;
readOnly?: boolean;
disabled?: boolean;
condition?: (...args: any[]) => any | void;
condition?: Condition;
components?: { [key: string]: React.ComponentType };
hidden?: boolean
}