Skip to content

Commit 063ef6b

Browse files
committed
feat(renderer): enhance conditions
- adds sequence condition - adds then, else branches - adds set action - optimize condition to listen values changes
1 parent 9cd85b8 commit 063ef6b

File tree

3 files changed

+147
-18
lines changed

3 files changed

+147
-18
lines changed

packages/react-form-renderer/demo/index.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,44 @@ const asyncValidatorSchema = {
4949
]
5050
};
5151

52+
const conditionLogicDemo = {
53+
title: 'Testing Conditions',
54+
description: 'Write X in both',
55+
fields: [
56+
{
57+
name: 'text_box_1',
58+
label: 'Text Box 1',
59+
title: 'Text Box',
60+
component: 'text-field',
61+
clearOnUnmount: true,
62+
condition: {
63+
sequence: [
64+
{ when: 'a', is: 'x', then: { visible: true, set: { password: 'defaultPassword' } }, else: { set: { password: 'no password' } } },
65+
{ when: 'b', is: 'x', then: { visible: true, set: { text_box_1: 'defaultText' } } }
66+
]
67+
}
68+
},
69+
{
70+
name: 'a',
71+
label: 'a',
72+
title: 'a',
73+
component: 'text-field'
74+
},
75+
{
76+
name: 'b',
77+
label: 'b',
78+
title: 'b',
79+
component: 'text-field'
80+
},
81+
{
82+
name: 'password',
83+
label: 'password',
84+
title: 'password',
85+
component: 'text-field'
86+
}
87+
]
88+
};
89+
5290
const App = () => {
5391
// const [values, setValues] = useState({});
5492
return (
@@ -57,7 +95,7 @@ const App = () => {
5795
validatorMapper={validatorMapper}
5896
componentMapper={componentMapper}
5997
onSubmit={(values) => console.log(values)}
60-
schema={asyncValidatorSchema}
98+
schema={conditionLogicDemo}
6199
FormTemplate={FormTemplate}
62100
actionMapper={actionMapper}
63101
schemaValidatorMapper={{

packages/react-form-renderer/src/form-renderer/condition.js

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import PropTypes from 'prop-types';
33
import lodashIsEmpty from 'lodash/isEmpty';
4-
import { FormSpy } from 'react-final-form';
54
import get from 'lodash/get';
5+
import isEqual from 'lodash/isEqual';
6+
7+
import useFormApi from '../files/use-form-api';
68

79
const isEmptyValue = (value) => (typeof value === 'number' || value === true ? false : lodashIsEmpty(value));
810

@@ -27,48 +29,125 @@ const fieldCondition = (value, { is, isNotEmpty, isEmpty, pattern, notMatch, fla
2729
};
2830

2931
export const parseCondition = (condition, values) => {
32+
let positiveResult = {
33+
visible: true,
34+
...condition.then
35+
};
36+
37+
let negativeResult = {
38+
visible: false,
39+
...condition.else
40+
};
41+
3042
if (Array.isArray(condition)) {
31-
return !condition.map((condition) => parseCondition(condition, values)).some((result) => result === false);
43+
return !condition.map((condition) => parseCondition(condition, values)).some((result) => result === false) ? positiveResult : negativeResult;
3244
}
3345

3446
if (condition.and) {
35-
return condition.and.map((condition) => parseCondition(condition, values)).every((result) => result === true);
47+
return !condition.and.map((condition) => parseCondition(condition, values)).some((result) => result === false) ? positiveResult : negativeResult;
48+
}
49+
50+
if (condition.sequence) {
51+
return condition.sequence.reduce(
52+
(acc, curr) => {
53+
const result = parseCondition(curr, values);
54+
55+
return {
56+
sets: [...acc.sets, ...[result.set ? result.set : []]],
57+
visible: acc.visible || result.visible
58+
};
59+
},
60+
{ ...negativeResult, sets: [] }
61+
);
3662
}
3763

3864
if (condition.or) {
39-
return condition.or.map((condition) => parseCondition(condition, values)).some((result) => result === true);
65+
return condition.or.map((condition) => parseCondition(condition, values)).some((result) => result === true) ? positiveResult : negativeResult;
4066
}
4167

4268
if (condition.not) {
43-
return !parseCondition(condition.not, values);
69+
return !parseCondition(condition.not, values) ? positiveResult : negativeResult;
4470
}
4571

4672
if (typeof condition.when === 'string') {
47-
return fieldCondition(get(values, condition.when), condition);
73+
return fieldCondition(get(values, condition.when), condition) ? positiveResult : negativeResult;
4874
}
4975

5076
if (Array.isArray(condition.when)) {
51-
return !!condition.when.map((fieldName) => fieldCondition(get(values, fieldName), condition)).find((condition) => !!condition);
77+
return condition.when.map((fieldName) => fieldCondition(get(values, fieldName), condition)).find((condition) => !!condition)
78+
? positiveResult
79+
: negativeResult;
5280
}
5381

54-
return false;
82+
return negativeResult;
5583
};
5684

57-
const Condition = ({ condition, children }) => <FormSpy>{({ values }) => (parseCondition(condition, values) ? children : null)}</FormSpy>;
85+
const Condition = React.memo(({ condition, children, values }) => {
86+
const formOptions = useFormApi();
87+
const dirty = formOptions.getState().dirty;
88+
89+
const [lastSets, setSets] = React.useState([]);
90+
91+
const conditionResult = parseCondition(condition, values, formOptions);
92+
const setters = conditionResult.set ? [conditionResult.set] : conditionResult.sets;
93+
94+
useEffect(() => {
95+
if (!dirty) {
96+
setSets([]);
97+
}
98+
}, [dirty]);
99+
100+
useEffect(() => {
101+
if (setters && setters.length > 0 && (!dirty || !isEqual(setters, lastSets))) {
102+
setters.forEach((setter, index) => {
103+
if (setter && (!dirty || !isEqual(setter, lastSets[index]))) {
104+
formOptions.batch(() => {
105+
Object.entries(setter).forEach(([name, value]) => {
106+
formOptions.change(name, value);
107+
});
108+
});
109+
}
110+
});
111+
setSets(setters);
112+
}
113+
}, [setters, dirty]);
114+
115+
return conditionResult.visible ? children : null;
116+
}, isEqual);
58117

59118
const conditionProps = {
60-
when: PropTypes.string.isRequired,
61-
is: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.object, PropTypes.number, PropTypes.bool]).isRequired,
119+
when: PropTypes.string,
120+
is: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.object, PropTypes.number, PropTypes.bool]),
62121
isNotEmpty: PropTypes.bool,
63122
isEmpty: PropTypes.bool,
64-
children: PropTypes.oneOf([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]).isRequired,
65123
pattern: PropTypes.oneOf([PropTypes.string, PropTypes.instanceOf(RegExp)]),
66-
notMatch: PropTypes.any
124+
notMatch: PropTypes.any,
125+
then: PropTypes.shape({
126+
visible: PropTypes.bool,
127+
set: PropTypes.object
128+
}),
129+
else: PropTypes.shape({
130+
visible: PropTypes.bool,
131+
set: PropTypes.object
132+
})
133+
};
134+
135+
const nestedConditions = {
136+
or: PropTypes.oneOfType([PropTypes.shape(conditionProps), PropTypes.arrayOf(PropTypes.shape(conditionProps))]),
137+
and: PropTypes.oneOfType([PropTypes.shape(conditionProps), PropTypes.arrayOf(PropTypes.shape(conditionProps))]),
138+
not: PropTypes.oneOfType([PropTypes.shape(conditionProps), PropTypes.arrayOf(PropTypes.shape(conditionProps))]),
139+
sequence: PropTypes.arrayOf(PropTypes.shape(conditionProps))
140+
};
141+
142+
const conditionsProps = {
143+
...conditionProps,
144+
...nestedConditions
67145
};
68146

69147
Condition.propTypes = {
70-
condition: PropTypes.oneOfType([PropTypes.shape(conditionProps), PropTypes.arrayOf(PropTypes.shape(conditionProps))]),
71-
children: PropTypes.oneOf([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]).isRequired
148+
condition: PropTypes.oneOfType([PropTypes.shape(conditionsProps), PropTypes.arrayOf(PropTypes.shape(conditionsProps))]),
149+
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]).isRequired,
150+
values: PropTypes.object.isRequired
72151
};
73152

74153
export default Condition;

packages/react-form-renderer/src/form-renderer/render-form.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { memoize } from '../validators/helpers';
88
import componentTypes from '../files/component-types';
99
import composeValidators from '../files/compose-validators';
1010
import convertInitialValue from './convert-initial-value';
11+
import FormSpy from '../files/form-spy';
1112

1213
const assignSpecialType = (componentType) => ([componentTypes.CHECKBOX, componentTypes.RADIO].includes(componentType) ? componentType : undefined);
1314

@@ -22,7 +23,18 @@ FormFieldHideWrapper.defaultProps = {
2223
hideField: false
2324
};
2425

25-
const FormConditionWrapper = ({ condition, children }) => (condition ? <Condition condition={condition}>{children}</Condition> : children);
26+
const FormConditionWrapper = ({ condition, children }) =>
27+
condition ? (
28+
<FormSpy>
29+
{({ values }) => (
30+
<Condition condition={condition} values={values}>
31+
{children}
32+
</Condition>
33+
)}
34+
</FormSpy>
35+
) : (
36+
children
37+
);
2638

2739
FormConditionWrapper.propTypes = {
2840
condition: PropTypes.object,

0 commit comments

Comments
 (0)