Skip to content

Commit 3dfc22e

Browse files
committed
fix(renderer): optimize condition rendering.
1 parent 750d8cc commit 3dfc22e

File tree

3 files changed

+583
-102
lines changed

3 files changed

+583
-102
lines changed

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

Lines changed: 121 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,131 @@
11
/* eslint-disable camelcase */
22
import React from 'react';
3+
import PropTypes from 'prop-types';
34
import ReactDOM from 'react-dom';
4-
import { FormRenderer, useFieldApi, componentTypes, validatorTypes } from '../src';
5-
import componentMapper from './form-fields-mapper';
6-
import FormTemplate from './form-template';
5+
import { FormRenderer, useFieldApi, componentTypes } from '../src';
6+
import MuiTextField from '@material-ui/core/TextField';
7+
import Grid from '@material-ui/core/Grid';
8+
9+
import { Button as MUIButton, Typography } from '@material-ui/core';
10+
import { makeStyles } from '@material-ui/core/styles';
11+
12+
import FormTemplate from '@data-driven-forms/common/form-template';
13+
14+
const useStyles = makeStyles(() => ({
15+
buttonGroup: {
16+
display: 'flex',
17+
justifyContent: 'flex-end',
18+
'&>button:not(last-child)': {
19+
marginLeft: 8
20+
}
21+
}
22+
}));
23+
24+
const Form = ({ children, GridContainerProps, GridProps, ...props }) => (
25+
<Grid item xs={12} {...GridProps}>
26+
<form noValidate {...props}>
27+
<Grid container item spacing={2} xs={12} {...GridContainerProps}>
28+
{children}
29+
</Grid>
30+
</form>
31+
</Grid>
32+
);
33+
34+
Form.propTypes = {
35+
children: PropTypes.node,
36+
GridProps: PropTypes.object,
37+
GridContainerProps: PropTypes.object
38+
};
39+
40+
const Description = ({ children, GridProps, ...props }) => (
41+
<Grid item xs={12} {...GridProps}>
42+
<Typography variant="body1" gutterBottom {...props}>
43+
{children}
44+
</Typography>
45+
</Grid>
46+
);
47+
48+
Description.propTypes = {
49+
children: PropTypes.node,
50+
GridProps: PropTypes.object
51+
};
52+
53+
const Title = ({ children, GridProps, ...props }) => (
54+
<Grid item xs={12} {...GridProps}>
55+
<Typography variant="h3" gutterBottom {...props}>
56+
{children}
57+
</Typography>
58+
</Grid>
59+
);
60+
61+
Title.propTypes = {
62+
children: PropTypes.node,
63+
GridProps: PropTypes.object
64+
};
65+
66+
const ButtonGroup = ({ children, GridProps, ...props }) => {
67+
const classes = useStyles();
68+
return (
69+
<Grid item xs={12} {...GridProps}>
70+
<div className={classes.buttonGroup} {...props}>
71+
{children}
72+
</div>
73+
</Grid>
74+
);
75+
};
76+
77+
ButtonGroup.propTypes = {
78+
children: PropTypes.node,
79+
GridProps: PropTypes.object
80+
};
81+
82+
const Button = ({ label, variant, children, buttonType, ...props }) => (
83+
<MUIButton color={variant} variant="contained" {...props}>
84+
{label || children}
85+
</MUIButton>
86+
);
87+
88+
Button.propTypes = {
89+
children: PropTypes.node,
90+
label: PropTypes.node,
91+
variant: PropTypes.string,
92+
buttonType: PropTypes.string
93+
};
94+
95+
const MuiFormTemplate = (props) => (
96+
<FormTemplate FormWrapper={Form} Button={Button} ButtonGroup={ButtonGroup} Title={Title} Description={Description} {...props} />
97+
);
98+
99+
export default MuiFormTemplate;
7100

8101
// eslint-disable-next-line react/prop-types
9102
const TextField = (props) => {
10-
const { input, label, isRequired } = useFieldApi(props);
103+
const { input, label, isRequired, WrapperProps } = useFieldApi(props);
11104
return (
12-
<div>
13-
<label>
14-
{label}
15-
{isRequired && '*'}
16-
</label>
17-
<input {...input} />
18-
</div>
105+
<Grid item xs={12} {...WrapperProps}>
106+
<MuiTextField {...input} label={label} required={isRequired} />
107+
</Grid>
19108
);
20109
};
21110

22-
let key;
23-
24-
const fileSchema = {
25-
fields: [
26-
{
27-
component: 'text-field',
28-
name: 'required',
29-
label: 'required'
30-
},
31-
{
32-
component: 'text-field',
33-
name: 'field',
34-
label: 'field',
35-
resolveProps: (y, x, formOptions) => {
36-
const value = formOptions.getFieldState('required')?.value;
37-
38-
//console.log({ value });
39-
40-
if (value) {
41-
key = key || Date.now();
42-
43-
return {
44-
isRequired: true,
45-
validate: [{ type: validatorTypes.REQUIRED }],
46-
key
47-
};
48-
} else {
49-
key = undefined;
50-
}
111+
const fields = [];
112+
113+
for (let index = 0; index < 1000; index++) {
114+
fields.push({
115+
name: `field-${index}`,
116+
label: `Text field ${index}`,
117+
component: 'text-field',
118+
...(index > 0 ? {
119+
condition: {
120+
when: `field-${index - 1}`,
121+
isEmpty: true
51122
}
52-
}
53-
]
123+
} : {})
124+
});
125+
}
126+
127+
const schema = {
128+
fields
54129
};
55130

56131
const App = () => {
@@ -59,13 +134,12 @@ const App = () => {
59134
<div style={{ padding: 20 }}>
60135
<FormRenderer
61136
componentMapper={{
62-
...componentMapper,
63137
[componentTypes.TEXT_FIELD]: TextField
64138
}}
65-
onSubmit={(values, ...args) => console.log(values, args)}
66-
FormTemplate={FormTemplate}
67-
schema={fileSchema}
68-
subscription={{ values: true }}
139+
onSubmit={console.log}
140+
FormTemplate={MuiFormTemplate}
141+
schema={schema}
142+
subscription={{ pristine: false }}
69143
/>
70144
</div>
71145
);

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

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,59 @@ import React, { useContext } from 'react';
22
import PropTypes from 'prop-types';
33
import RendererContext from '../renderer-context';
44
import Condition from '../condition';
5-
import FormSpy from '../form-spy';
5+
import { memoize } from '../common/helpers';
6+
import { Field } from 'react-final-form';
7+
8+
const mergeFunctionTrigger = (fn, field) => {
9+
let internalTriggers = [];
10+
const internalWhen = fn(field);
11+
if (Array.isArray(internalWhen)) {
12+
internalTriggers = [...internalWhen];
13+
} else {
14+
internalTriggers.push(internalWhen);
15+
}
16+
17+
return internalTriggers;
18+
};
19+
20+
const getConditionTriggers = memoize((condition, field) => {
21+
let triggers = [];
22+
if (Array.isArray(condition)) {
23+
return condition.reduce((acc, item) => [...acc, ...getConditionTriggers(item, field)], []);
24+
}
25+
26+
const {when, ...rest} = condition;
27+
const nestedKeys = ['and', 'or', 'sequence'];
28+
if (typeof when === 'string') {
29+
triggers = [...triggers, when];
30+
}
31+
32+
if (typeof when === 'function') {
33+
triggers = [...triggers, ...mergeFunctionTrigger(when, field)];
34+
}
35+
36+
if (Array.isArray(when)) {
37+
when.forEach(item => {
38+
if (typeof item === 'string') {
39+
triggers = [...triggers, item];
40+
}
41+
42+
if (typeof item === 'function') {
43+
triggers = [...triggers, ...mergeFunctionTrigger(item, field)];
44+
}
45+
});
46+
}
47+
48+
nestedKeys.forEach(key => {
49+
if (typeof rest[key] !== 'undefined') {
50+
rest[key].forEach(item => {
51+
triggers = [...triggers, ...getConditionTriggers(item, field)];
52+
});
53+
}
54+
});
55+
56+
return Array.from(new Set(triggers));
57+
});
658

759
const FormFieldHideWrapper = ({ hideField, children }) => (hideField ? <div hidden>{children}</div> : children);
860

@@ -15,18 +67,66 @@ FormFieldHideWrapper.defaultProps = {
1567
hideField: false
1668
};
1769

18-
const FormConditionWrapper = ({ condition, children, field }) =>
19-
condition ? (
20-
<FormSpy subscription={{ values: true }}>
21-
{({ values }) => (
22-
<Condition condition={condition} values={values} field={field}>
23-
{children}
24-
</Condition>
70+
const ConditionTriggerWrapper = ({condition, values, children, field}) => (
71+
<Condition condition={condition} values={values} field={field}>
72+
{children}
73+
</Condition>
74+
);
75+
76+
ConditionTriggerWrapper.propTypes = {
77+
condition: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
78+
children: PropTypes.node.isRequired,
79+
field: PropTypes.object,
80+
values: PropTypes.object.isRequired,
81+
};
82+
83+
const ConditionTriggerDetector = ({ values = {}, triggers = [], children, condition, field }) => {
84+
const internalTriggers = [...triggers];
85+
if (internalTriggers.length === 0) {
86+
return (
87+
<ConditionTriggerWrapper condition={condition} values={values} field={field}>
88+
{children}
89+
</ConditionTriggerWrapper>
90+
);
91+
}
92+
93+
const name = internalTriggers.shift();
94+
return (
95+
<Field name={name} subscription={{ value: true }}>
96+
{({input: {value}}) =>(
97+
<ConditionTriggerDetector
98+
triggers={[...internalTriggers]}
99+
values={{...values, [name]: value}}
100+
condition={condition}
101+
field={field}
102+
>
103+
{children}
104+
</ConditionTriggerDetector>
25105
)}
26-
</FormSpy>
27-
) : (
28-
children
106+
</Field>
107+
);
108+
};
109+
110+
ConditionTriggerDetector.propTypes = {
111+
values: PropTypes.object,
112+
triggers: PropTypes.arrayOf(PropTypes.string),
113+
children: PropTypes.node,
114+
condition: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
115+
field: PropTypes.object.isRequired
116+
};
117+
118+
const FormConditionWrapper = ({ condition, children, field }) => {
119+
if (condition) {
120+
const triggers = getConditionTriggers(condition, field);
121+
return (
122+
<ConditionTriggerDetector triggers={triggers} condition={condition} field={field}>
123+
{children}
124+
</ConditionTriggerDetector>
29125
);
126+
}
127+
128+
return children;
129+
};
30130

31131
FormConditionWrapper.propTypes = {
32132
condition: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
@@ -111,6 +211,8 @@ SingleField.propTypes = {
111211
resolveProps: PropTypes.func
112212
};
113213

114-
const renderForm = (fields) => fields.map((field) => (Array.isArray(field) ? renderForm(field) : <SingleField key={field.name} {...field} />));
214+
const renderForm = (fields) => {
215+
return fields.map((field) => (Array.isArray(field) ? renderForm(field) : <SingleField key={field.name} {...field} />));
216+
};
115217

116218
export default renderForm;

0 commit comments

Comments
 (0)