Skip to content

Commit 2964ca2

Browse files
committed
fix(ant): Added field array component
1 parent 06693e7 commit 2964ca2

File tree

3 files changed

+288
-3
lines changed

3 files changed

+288
-3
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const arraySchemaDDF = {
2+
title: 'FieldArray',
3+
fields: [
4+
{
5+
component: 'field-array',
6+
name: 'nicePeople',
7+
fieldKey: 'field_array',
8+
label: 'Nice people',
9+
description: 'This allow you to add nice people to the list dynamically',
10+
defaultItem: { name: 'enter a name', lastName: 'enter a last name' },
11+
fields: [
12+
{
13+
component: 'text-field',
14+
name: 'name',
15+
label: 'Name',
16+
placeholder: 'Borek',
17+
isRequired: true,
18+
validate: [
19+
{
20+
type: 'required'
21+
}
22+
]
23+
},
24+
{
25+
component: 'text-field',
26+
name: 'lastName',
27+
label: 'Last Name',
28+
placeholder: 'Stavitel'
29+
}
30+
]
31+
},
32+
{
33+
component: 'field-array',
34+
name: 'minItems',
35+
label: 'A list with a minimal number of items',
36+
validate: [{ type: 'min-items', threshold: 3 }],
37+
fields: [
38+
{
39+
component: 'text-field',
40+
label: 'Item'
41+
}
42+
]
43+
},
44+
{
45+
component: 'field-array',
46+
name: 'number',
47+
defaultItem: 5,
48+
label: 'Default value with initialValues set',
49+
fields: [
50+
{
51+
component: 'text-field',
52+
label: 'Item',
53+
type: 'number'
54+
}
55+
]
56+
},
57+
{
58+
component: 'field-array',
59+
name: 'minMax',
60+
minItems: 4,
61+
maxItems: 6,
62+
label: 'Min 4 item, max 6 items without validators',
63+
fields: [
64+
{
65+
component: 'text-field',
66+
isRequired: true,
67+
validate: [
68+
{
69+
type: 'required'
70+
}
71+
]
72+
}
73+
]
74+
}
75+
]
76+
};
77+
78+
export default arraySchemaDDF;

packages/ant-component-mapper/demo/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import dualListSelectSchema from './demo-schemas/dual-list-select-schema'
99
import { componentMapper, FormTemplate } from '../src';
1010
import wizardSchema from './demo-schemas/wizard-schema';
1111
import sliderSchema from './demo-schemas/slider-schema';
12+
import fieldArraySchema from './demo-schemas/field-array-schema';
1213

1314
const style = {
1415
position: 'relative',
@@ -22,7 +23,7 @@ const App = () => (
2223
componentMapper={componentMapper}
2324
FormTemplate={(props) => <FormTemplate layout='vertical' {...props} />}
2425
onSubmit={console.log}
25-
schema={sliderSchema}
26+
schema={fieldArraySchema}
2627
/>
2728
</div>
2829
);
Lines changed: 208 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,209 @@
1-
import React from 'react';
1+
import React, { useReducer } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { useFieldApi, useFormApi, FieldArray } from '@data-driven-forms/react-form-renderer';
4+
import { Row, Col, Button, Typography, Space } from 'antd';
5+
import { UndoOutlined, RedoOutlined } from '@ant-design/icons';
26

3-
export default () => <div>not implemented</div>;
7+
import AntForm from '../common/form-wrapper';
8+
9+
const ArrayItem = ({ fields, fieldIndex, name, remove, length, minItems, removeLabel }) => {
10+
const { renderForm } = useFormApi();
11+
12+
const editedFields = fields.map((field, index) => {
13+
const computedName = field.name ? `${name}.${field.name}` : name;
14+
return { ...field, name: computedName, key: `${computedName}-${index}` };
15+
});
16+
17+
return (
18+
<Row>
19+
<Col span={24}>{renderForm([editedFields])}</Col>
20+
<Col span={24}>
21+
<Button type="primary" danger onClick={() => remove(fieldIndex)} disabled={length <= minItems}>
22+
{removeLabel}
23+
</Button>
24+
</Col>
25+
</Row>
26+
);
27+
};
28+
29+
ArrayItem.propTypes = {
30+
name: PropTypes.string,
31+
fieldIndex: PropTypes.number.isRequired,
32+
fields: PropTypes.arrayOf(PropTypes.object),
33+
remove: PropTypes.func.isRequired,
34+
length: PropTypes.number,
35+
minItems: PropTypes.number,
36+
removeLabel: PropTypes.node.isRequired
37+
};
38+
39+
const defaultButtonLabels = {
40+
add: 'ADD',
41+
remove: 'REMOVE'
42+
};
43+
44+
const initialState = {
45+
index: 0,
46+
history: []
47+
};
48+
49+
export const reducer = (state, { type, action }) => {
50+
switch (type) {
51+
case 'redo':
52+
return {
53+
...state,
54+
index: state.index + 1
55+
};
56+
case 'action':
57+
return {
58+
index: state.index + 1,
59+
history: [...state.history.slice(0, state.index), action]
60+
};
61+
case 'undo':
62+
return {
63+
...state,
64+
index: state.index - 1
65+
};
66+
case 'resetHistory':
67+
return {
68+
...state,
69+
history: state.history.slice(0, state.index)
70+
};
71+
default:
72+
return state;
73+
}
74+
};
75+
76+
const DynamicArray = ({ ...props }) => {
77+
const {
78+
arrayValidator,
79+
label,
80+
description,
81+
fields: formFields,
82+
defaultItem,
83+
meta,
84+
minItems,
85+
maxItems,
86+
noItemsMessage,
87+
FormFieldGridProps,
88+
FormControlProps,
89+
buttonLabels,
90+
validateOnMount,
91+
isRequired,
92+
helperText,
93+
FormItemProps,
94+
...rest
95+
} = useFieldApi(props);
96+
const [state, dispatch] = useReducer(reducer, initialState);
97+
98+
const combinedButtonLabels = {
99+
...defaultButtonLabels,
100+
...buttonLabels
101+
};
102+
103+
const { dirty, submitFailed, error } = meta;
104+
const isError = (dirty || submitFailed) && error && typeof error === 'string';
105+
return (
106+
<AntForm
107+
meta={{ ...meta, error: typeof error === 'object' ? error.name : error }}
108+
validateOnMount={validateOnMount}
109+
helperText={helperText}
110+
FormItemProps={FormItemProps}
111+
isRequired={isRequired}
112+
>
113+
<FieldArray key={rest.input.name} name={rest.input.name} validate={arrayValidator}>
114+
{({ fields: { map, value = [], push, remove } }) => {
115+
const pushWrapper = () => {
116+
dispatch({ type: 'resetHistory' });
117+
push(defaultItem);
118+
};
119+
120+
const removeWrapper = (index) => {
121+
dispatch({ type: 'action', action: { action: 'remove', value: value[index] } });
122+
remove(index);
123+
};
124+
125+
const undo = () => {
126+
push(state.history[state.index - 1].value);
127+
dispatch({ type: 'undo' });
128+
};
129+
130+
const redo = () => {
131+
remove(value.length - 1);
132+
dispatch({ type: 'redo' });
133+
};
134+
135+
return (
136+
<Row gutter={[0, 16]}>
137+
<Col span={24}>
138+
<Row justify="space-between">
139+
<Col>{label && <Typography.Title level={4}>{label}</Typography.Title>}</Col>
140+
<Col>
141+
<Space>
142+
<Button type="default" onClick={undo} disabled={state.index === 0} icon={<UndoOutlined />} />
143+
<Button type="default" onClick={redo} disabled={state.index === state.history.length} icon={<RedoOutlined />} />
144+
<Button type="primary" onClick={pushWrapper} disabled={value.length >= maxItems}>
145+
{combinedButtonLabels.add}
146+
</Button>
147+
</Space>
148+
</Col>
149+
</Row>
150+
</Col>
151+
{description && (
152+
<Col span={24}>
153+
<Typography.Text>{description}</Typography.Text>
154+
</Col>
155+
)}
156+
<Col span={24}>
157+
<Row gutter={[0, 16]}>
158+
{value.length <= 0 ? (
159+
<Typography.Text>{noItemsMessage}</Typography.Text>
160+
) : (
161+
map((name, index) => (
162+
<Col span={24} key={name}>
163+
<ArrayItem
164+
fields={formFields}
165+
name={name}
166+
fieldIndex={index}
167+
remove={removeWrapper}
168+
length={value.length}
169+
minItems={minItems}
170+
removeLabel={combinedButtonLabels.remove}
171+
/>
172+
</Col>
173+
))
174+
)}
175+
</Row>
176+
</Col>
177+
{isError && (
178+
<Col span={12}>
179+
<Typography.Text type="danger">{typeof error === 'object' ? error.name : error}</Typography.Text>
180+
</Col>
181+
)}
182+
</Row>
183+
);
184+
}}
185+
</FieldArray>
186+
</AntForm>
187+
);
188+
};
189+
190+
DynamicArray.propTypes = {
191+
label: PropTypes.node,
192+
description: PropTypes.node,
193+
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
194+
defaultItem: PropTypes.any,
195+
minItems: PropTypes.number,
196+
maxItems: PropTypes.number,
197+
noItemsMessage: PropTypes.node,
198+
FormItemProps: PropTypes.object,
199+
buttonLabels: PropTypes.object
200+
};
201+
202+
DynamicArray.defaultProps = {
203+
maxItems: Infinity,
204+
minItems: 0,
205+
noItemsMessage: 'No items added',
206+
FormItemProps: {}
207+
};
208+
209+
export default DynamicArray;

0 commit comments

Comments
 (0)