Skip to content

Commit 2dce5f0

Browse files
authored
Merge pull request #1429 from data-driven-forms/conditional-field-array
fix(renderer): add mapped conditions to condition triggers
2 parents 3c2dd06 + f92c49a commit 2dce5f0

File tree

7 files changed

+69
-21
lines changed

7 files changed

+69
-21
lines changed

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ ConditionTriggerDetector.propTypes = {
6767
field: PropTypes.object.isRequired,
6868
};
6969

70-
const FormConditionWrapper = ({ condition, children, field }) => {
70+
const FormConditionWrapper = ({ condition, children, field, conditionMapper }) => {
7171
if (condition) {
72-
const triggers = getConditionTriggers(condition, field);
72+
const triggers = getConditionTriggers(condition, field, conditionMapper);
7373
return (
7474
<ConditionTriggerDetector triggers={triggers} condition={condition} field={field}>
7575
{children}
@@ -84,10 +84,11 @@ FormConditionWrapper.propTypes = {
8484
condition: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
8585
children: PropTypes.node.isRequired,
8686
field: PropTypes.object,
87+
conditionMapper: PropTypes.object,
8788
};
8889

8990
const SingleField = ({ component, ...rest }) => {
90-
const { actionMapper, componentMapper } = useContext(RendererContext);
91+
const { actionMapper, componentMapper, conditionMapper } = useContext(RendererContext);
9192

9293
const { componentProps, Component, overrideProps, mergedResolveProps } = prepareComponentProps({ component, rest, componentMapper, actionMapper });
9394

@@ -98,7 +99,7 @@ const SingleField = ({ component, ...rest }) => {
9899
};
99100

100101
return (
101-
<FormConditionWrapper condition={condition} field={restProps}>
102+
<FormConditionWrapper condition={condition} field={restProps} conditionMapper={conditionMapper}>
102103
<FormFieldHideWrapper hideField={hideField}>
103104
<Component {...restProps} />
104105
</FormFieldHideWrapper>
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { Field } from "../common-types";
12
import { ConditionDefinition } from "../condition";
3+
import { ConditionMapper } from "../form-renderer/condition-mapper";
24

3-
declare function getConditionTriggers(params:ConditionDefinition | ConditionDefinition[]): string[];
5+
declare function getConditionTriggers(params:ConditionDefinition | ConditionDefinition[], field: Extract<Field, 'condition' | 'hideField'>, conditionMapper: ConditionMapper): string[];
46

57
export default getConditionTriggers;

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,27 @@ const mergeFunctionTrigger = (fn, field) => {
1010
return internalTriggers;
1111
};
1212

13-
const getConditionTriggers = (condition, field) => {
13+
const getConditionTriggers = (condition, field, conditionMapper = {}) => {
1414
let triggers = [];
1515
if (Array.isArray(condition)) {
16-
return condition.reduce((acc, item) => [...acc, ...getConditionTriggers(item, field)], []);
16+
return condition.reduce((acc, item) => [...acc, ...getConditionTriggers(item, field, conditionMapper)], []);
17+
}
18+
19+
// extract mapped attributes to a new static condition object
20+
if (typeof condition.mappedAttributes === 'object') {
21+
try {
22+
const newCondition = { ...condition, mappedAttributes: undefined };
23+
Object.entries(condition.mappedAttributes).forEach(([attribute, [functionName, ...args]]) => {
24+
if (!conditionMapper[functionName]) {
25+
throw new Error(`Missing condition mapper function "${functionName}" for field ${field.name}!`);
26+
}
27+
28+
newCondition[attribute] = conditionMapper[functionName](...args);
29+
});
30+
return getConditionTriggers(newCondition, field, conditionMapper);
31+
} catch (error) {
32+
console.error(error.toString());
33+
}
1734
}
1835

1936
const { when, ...rest } = condition;
@@ -41,13 +58,13 @@ const getConditionTriggers = (condition, field) => {
4158
nestedKeys.forEach((key) => {
4259
if (typeof rest[key] !== 'undefined') {
4360
rest[key].forEach((item) => {
44-
triggers = [...triggers, ...getConditionTriggers(item, field)];
61+
triggers = [...triggers, ...getConditionTriggers(item, field, conditionMapper)];
4562
});
4663
}
4764
});
4865

4966
if (typeof condition.not === 'object') {
50-
triggers = [...triggers, ...getConditionTriggers(condition.not, field)];
67+
triggers = [...triggers, ...getConditionTriggers(condition.not, field, conditionMapper)];
5168
}
5269

5370
return Array.from(new Set(triggers));

packages/react-form-renderer/src/get-visible-fields/get-visible-fields.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import parseCondition from '../parse-condition';
22

3-
const getVisibleFields = (schema, values) => {
3+
const getVisibleFields = (schema, values, conditionMapper) => {
44
if (Array.isArray(schema)) {
5-
return schema.map((field) => getVisibleFields(field, values)).filter(Boolean);
5+
return schema.map((field) => getVisibleFields(field, values, undefined, conditionMapper)).filter(Boolean);
66
}
77

88
if (schema.condition) {
9-
const result = parseCondition(schema.condition, values, schema);
9+
const result = parseCondition(schema.condition, values, schema, conditionMapper);
1010

1111
if (result.visible) {
1212
return {
1313
...schema,
14-
...(schema.fields && { fields: getVisibleFields(schema.fields, values).filter(Boolean) }),
14+
...(schema.fields && { fields: getVisibleFields(schema.fields, values, undefined, conditionMapper).filter(Boolean) }),
1515
};
1616
} else {
1717
return null;
@@ -20,7 +20,7 @@ const getVisibleFields = (schema, values) => {
2020

2121
return {
2222
...schema,
23-
...(schema.fields && { fields: getVisibleFields(schema.fields, values).filter(Boolean) }),
23+
...(schema.fields && { fields: getVisibleFields(schema.fields, values, undefined, conditionMapper).filter(Boolean) }),
2424
};
2525
};
2626

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,23 @@ export const parseCondition = (condition, values, field, conditionMapper = {}) =
8989
};
9090

9191
if (Array.isArray(condition)) {
92-
return !condition.map((condition) => parseCondition(condition, values, field)).some(({ result }) => result === false)
92+
return !condition.map((condition) => parseCondition(condition, values, field, conditionMapper)).some(({ result }) => result === false)
9393
? positiveResult
9494
: negativeResult;
9595
}
9696

9797
const conditionInternal = unpackMappedCondition(condition, conditionMapper);
9898

9999
if (conditionInternal.and) {
100-
return !conditionInternal.and.map((condition) => parseCondition(condition, values, field)).some(({ result }) => result === false)
100+
return !conditionInternal.and.map((condition) => parseCondition(condition, values, field, conditionMapper)).some(({ result }) => result === false)
101101
? positiveResult
102102
: negativeResult;
103103
}
104104

105105
if (conditionInternal.sequence) {
106106
return conditionInternal.sequence.reduce(
107107
(acc, curr) => {
108-
const result = parseCondition(curr, values, field);
108+
const result = parseCondition(curr, values, field, conditionMapper);
109109

110110
return {
111111
sets: [...acc.sets, ...(result.set ? [result.set] : [])],
@@ -118,13 +118,13 @@ export const parseCondition = (condition, values, field, conditionMapper = {}) =
118118
}
119119

120120
if (conditionInternal.or) {
121-
return conditionInternal.or.map((condition) => parseCondition(condition, values, field)).some(({ result }) => result === true)
121+
return conditionInternal.or.map((condition) => parseCondition(condition, values, field, conditionMapper)).some(({ result }) => result === true)
122122
? positiveResult
123123
: negativeResult;
124124
}
125125

126126
if (conditionInternal.not) {
127-
return !parseCondition(conditionInternal.not, values, field).result ? positiveResult : negativeResult;
127+
return !parseCondition(conditionInternal.not, values, field, conditionMapper).result ? positiveResult : negativeResult;
128128
}
129129

130130
const finalWhen = typeof conditionInternal.when === 'function' ? conditionInternal.when(field) : conditionInternal.when;

packages/react-form-renderer/src/tests/get-condition-triggers/get-condition-triggers.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,32 @@ describe('getConditionTriggers', () => {
3232
test('should extract name from array of conditions', () => {
3333
expect(getConditionTriggers([{ when: 'a' }, { when: 'b' }])).toEqual(['a', 'b']);
3434
});
35+
36+
describe('mappedAttributes', () => {
37+
it(`should extract the 'when' attribute from mappedAttributes`, () => {
38+
expect(
39+
getConditionTriggers(
40+
{ mappedAttributes: { when: ['equals', 'a'] } },
41+
{},
42+
{
43+
equals: (a) => () => a,
44+
}
45+
)
46+
).toEqual(['a']);
47+
});
48+
it(`should log error if mapped condition is missing from condition mapper`, () => {
49+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
50+
expect(
51+
getConditionTriggers(
52+
{ mappedAttributes: { when: ['equals', 'a'] } },
53+
{ name: 'foo' },
54+
// conditionMapper is deliberately empty
55+
{}
56+
)
57+
).toEqual([]);
58+
expect(consoleSpy).toHaveBeenCalledTimes(1);
59+
expect(consoleSpy).toHaveBeenCalledWith(`Error: Missing condition mapper function "equals" for field foo!`);
60+
consoleSpy.mockRestore();
61+
});
62+
});
3563
});

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const validation = async (schema, options) => {
3232
throw new Error(`options argument has to be type of object, provided: ${typeof options}`);
3333
}
3434

35-
const { values, componentMapper, validatorMapper, actionMapper, schemaValidatorMapper, omitWarnings } = options;
35+
const { values, componentMapper, validatorMapper, actionMapper, schemaValidatorMapper, omitWarnings, conditionMapper } = options;
3636

3737
const validatorMapperMerged = { ...defaultValidatorMapper, ...validatorMapper };
3838

@@ -49,7 +49,7 @@ const validation = async (schema, options) => {
4949

5050
defaultSchemaValidator(finalSchema, finalComponentMapper, validatorTypes, actionTypes, schemaValidatorMapper);
5151

52-
finalSchema = getVisibleFields(finalSchema, values);
52+
finalSchema = getVisibleFields(finalSchema, values, undefined, conditionMapper);
5353

5454
const validates = getValidates(finalSchema, { componentMapper: finalComponentMapper, actionMapper, values });
5555

0 commit comments

Comments
 (0)