feat: remembers conditional field values after removing / readding
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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> = {
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user