fix: more advanced conditional logic edge cases
This commit is contained in:
@@ -23,7 +23,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
if (fieldSchema) {
|
||||
const validationPromises = [];
|
||||
|
||||
const structureFieldState = (field, data = {}) => {
|
||||
const structureFieldState = (field, passesCondition, data = {}) => {
|
||||
const value = typeof data?.[field.name] !== 'undefined' ? data[field.name] : field.defaultValue;
|
||||
|
||||
const fieldState = {
|
||||
@@ -31,6 +31,8 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
initialValue: value,
|
||||
valid: true,
|
||||
validate: field.validate,
|
||||
condition: field.admin?.condition,
|
||||
passesCondition,
|
||||
};
|
||||
|
||||
validationPromises.push(buildValidationPromise(fieldState, field));
|
||||
@@ -38,7 +40,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
return fieldState;
|
||||
};
|
||||
|
||||
const iterateFields = (fields: FieldSchema[], data: Data, path = '') => fields.reduce((state, field) => {
|
||||
const iterateFields = (fields: FieldSchema[], data: Data, parentPassesCondition: boolean, path = '') => fields.reduce((state, field) => {
|
||||
let initialData = data;
|
||||
|
||||
if (!field?.admin?.disabled) {
|
||||
@@ -46,6 +48,8 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
initialData = { [field.name]: field.defaultValue };
|
||||
}
|
||||
|
||||
const passesCondition = (field?.admin?.condition ? field.admin.condition(fullData || {}, initialData || {}) : true) && parentPassesCondition;
|
||||
|
||||
if (field.name) {
|
||||
if (field.type === 'relationship' && initialData?.[field.name] === null) {
|
||||
initialData[field.name] = 'null';
|
||||
@@ -68,7 +72,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
initialValue: row.id || new ObjectID().toHexString(),
|
||||
valid: true,
|
||||
},
|
||||
...iterateFields(field.fields, row, rowPath),
|
||||
...iterateFields(field.fields, row, passesCondition, rowPath),
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
@@ -97,7 +101,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
initialValue: row.id || new ObjectID().toHexString(),
|
||||
valid: true,
|
||||
},
|
||||
...(block?.fields ? iterateFields(block.fields, row, rowPath) : {}),
|
||||
...(block?.fields ? iterateFields(block.fields, row, passesCondition, rowPath) : {}),
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
@@ -113,13 +117,13 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
|
||||
return {
|
||||
...state,
|
||||
...iterateFields(field.fields, subFieldData, `${path}${field.name}.`),
|
||||
...iterateFields(field.fields, subFieldData, passesCondition, `${path}${field.name}.`),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
[`${path}${field.name}`]: structureFieldState(field, data),
|
||||
[`${path}${field.name}`]: structureFieldState(field, passesCondition, data),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -127,21 +131,21 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
if (field.type === 'row') {
|
||||
return {
|
||||
...state,
|
||||
...iterateFields(field.fields, data, path),
|
||||
...iterateFields(field.fields, data, passesCondition, path),
|
||||
};
|
||||
}
|
||||
|
||||
// Handle normal fields
|
||||
return {
|
||||
...state,
|
||||
[`${path}${field.name}`]: structureFieldState(field, data),
|
||||
[`${path}${field.name}`]: structureFieldState(field, passesCondition, data),
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}, {});
|
||||
|
||||
const resultingState = iterateFields(fieldSchema, fullData);
|
||||
const resultingState = iterateFields(fieldSchema, fullData, true);
|
||||
await Promise.all(validationPromises);
|
||||
return resultingState;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { unflatten, flatten } from 'flatley';
|
||||
import flattenFilters from './flattenFilters';
|
||||
import getSiblingData from './getSiblingData';
|
||||
import reduceFieldsToValues from './reduceFieldsToValues';
|
||||
import { Fields } from './types';
|
||||
|
||||
const unflattenRowsFromState = (state: Fields, path) => {
|
||||
@@ -106,21 +108,31 @@ function fieldReducer(state: Fields, action): Fields {
|
||||
case 'MODIFY_CONDITION': {
|
||||
const { path, result } = action;
|
||||
|
||||
return Object.entries(state).reduce((newState, [key, val]) => {
|
||||
if (key === path || key.indexOf(`${path}.`) === 0) {
|
||||
return Object.entries(state).reduce((newState, [fieldPath, field]) => {
|
||||
if (fieldPath === path || fieldPath.indexOf(`${path}.`) === 0) {
|
||||
let passesCondition = result;
|
||||
|
||||
// If a condition is being set to true,
|
||||
// Set all conditions to true
|
||||
// Besides those who still fail their own conditions
|
||||
|
||||
if (passesCondition && field.condition) {
|
||||
passesCondition = field.condition(reduceFieldsToValues(state), getSiblingData(state, path));
|
||||
}
|
||||
|
||||
return {
|
||||
...newState,
|
||||
[key]: {
|
||||
...val,
|
||||
passesCondition: result,
|
||||
[fieldPath]: {
|
||||
...field,
|
||||
passesCondition,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...newState,
|
||||
[key]: {
|
||||
...val,
|
||||
[fieldPath]: {
|
||||
...field,
|
||||
},
|
||||
};
|
||||
}, {});
|
||||
@@ -136,6 +148,7 @@ function fieldReducer(state: Fields, action): Fields {
|
||||
initialValue: action.initialValue,
|
||||
stringify: action.stringify,
|
||||
validate: action.validate,
|
||||
condition: action.condition,
|
||||
passesCondition: action.passesCondition,
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
passesCondition?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
admin: {
|
||||
readOnly,
|
||||
description,
|
||||
condition,
|
||||
},
|
||||
} = props;
|
||||
|
||||
@@ -70,6 +71,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
ignoreWhileFlattening: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const addRow = useCallback(async (rowIndex) => {
|
||||
|
||||
@@ -46,6 +46,7 @@ const Blocks: React.FC<Props> = (props) => {
|
||||
admin: {
|
||||
readOnly,
|
||||
description,
|
||||
condition,
|
||||
},
|
||||
} = props;
|
||||
|
||||
@@ -80,6 +81,7 @@ const Blocks: React.FC<Props> = (props) => {
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
ignoreWhileFlattening: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const addRow = useCallback(async (rowIndex, blockType) => {
|
||||
|
||||
@@ -25,6 +25,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
style,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
} = props;
|
||||
|
||||
@@ -44,6 +45,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
condition,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,6 +25,7 @@ const Code: React.FC<Props> = (props) => {
|
||||
width,
|
||||
language,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
label,
|
||||
minLength,
|
||||
@@ -55,6 +56,7 @@ const Code: React.FC<Props> = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -27,6 +27,7 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
width,
|
||||
date,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
} = props;
|
||||
|
||||
@@ -45,6 +46,7 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
} = useFieldType({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -22,6 +22,7 @@ const Email: React.FC<Props> = (props) => {
|
||||
placeholder,
|
||||
autoComplete,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
label,
|
||||
} = props;
|
||||
@@ -37,6 +38,7 @@ const Email: React.FC<Props> = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -25,6 +25,7 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
step,
|
||||
placeholder,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
} = props;
|
||||
|
||||
@@ -44,6 +45,7 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const handleChange = useCallback((e) => {
|
||||
|
||||
@@ -27,6 +27,7 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
style,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
options,
|
||||
} = props;
|
||||
@@ -46,6 +47,7 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
} = useFieldType({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -37,6 +37,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
style,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
} = props;
|
||||
|
||||
@@ -73,6 +74,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
} = useFieldType({
|
||||
path: path || name,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const addOptions = useCallback((data, relation) => {
|
||||
|
||||
@@ -46,6 +46,7 @@ const RichText: React.FC<Props> = (props) => {
|
||||
placeholder,
|
||||
description,
|
||||
hideGutter,
|
||||
condition,
|
||||
} = {},
|
||||
} = props;
|
||||
|
||||
@@ -106,6 +107,7 @@ const RichText: React.FC<Props> = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
stringify: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -38,6 +38,7 @@ const Select: React.FC<Props> = (props) => {
|
||||
style,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
} = props;
|
||||
|
||||
@@ -58,6 +59,7 @@ const Select: React.FC<Props> = (props) => {
|
||||
} = useFieldType({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -22,6 +22,7 @@ const Text: React.FC<Props> = (props) => {
|
||||
style,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
} = props;
|
||||
|
||||
@@ -31,6 +32,7 @@ const Text: React.FC<Props> = (props) => {
|
||||
path,
|
||||
validate,
|
||||
enableDebouncedValue: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -22,6 +22,7 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
placeholder,
|
||||
rows,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
label,
|
||||
minLength,
|
||||
@@ -44,6 +45,7 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
condition,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -32,6 +32,7 @@ const Upload: React.FC<Props> = (props) => {
|
||||
style,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
label,
|
||||
validate = upload,
|
||||
@@ -53,6 +54,7 @@ const Upload: React.FC<Props> = (props) => {
|
||||
const fieldType = useFieldType({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -13,6 +13,7 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
disableFormData,
|
||||
ignoreWhileFlattening,
|
||||
stringify,
|
||||
condition,
|
||||
} = options;
|
||||
|
||||
const formContext = useForm();
|
||||
@@ -48,6 +49,7 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
ignoreWhileFlattening,
|
||||
initialValue,
|
||||
validate,
|
||||
condition,
|
||||
value: valueToSend,
|
||||
valid: false,
|
||||
errorMessage: undefined,
|
||||
@@ -63,7 +65,7 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
}
|
||||
|
||||
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 { Condition, Validate } 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> = {
|
||||
|
||||
Reference in New Issue
Block a user