I have a dynamic form where I can add multiple form fields. Now I want to re-order form fields. For example I have below code where I can add fields as sight 1, sight 2, sight 3 and so on with there respective price fields. Now I want to move sight 3 in the middle of 1 and 2. How can I implement a function to create move up and down buttons and on click, I can move that row up or down.
Here is my code:
import React from "react";
import "./index.css";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
import { Button, Form, Input, Select, Space } from "antd";
const { Option } = Select;
const areas = [
{ label: "Beijing", value: "Beijing" },
{ label: "Shanghai", value: "Shanghai" }
];
const sights = {
Beijing: ["Tiananmen", "Great Wall"],
Shanghai: ["Oriental Pearl", "The Bund"]
};
type SightsKeys = keyof typeof sights;
const App: React.FC = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log("Received values of form:", values);
};
const handleChange = () => {
form.setFieldsValue({ sights: [] });
};
return (
<Form
form={form}
name="dynamic_form_complex"
onFinish={onFinish}
style={{ maxWidth: 600 }}
autoComplete="off"
>
<Form.Item
name="area"
label="Area"
rules={[{ required: true, message: "Missing area" }]}
>
<Select options={areas} onChange={handleChange} />
</Form.Item>
<Form.List name="sights">
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Space key={field.key} align="baseline">
<Form.Item
noStyle
shouldUpdate={(prevValues, curValues) =>
prevValues.area !== curValues.area ||
prevValues.sights !== curValues.sights
}
>
{() => (
<Form.Item
{...field}
label={`Sight ${field.name + 1}`}
name={[field.name, "sight"]}
rules={[{ required: true, message: "Missing sight" }]}
>
<Select
disabled={!form.getFieldValue("area")}
style={{ width: 130 }}
>
{(
sights[form.getFieldValue("area") as SightsKeys] || []
).map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</Form.Item>
)}
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prev, cur) =>
prev?.sights[field?.name]?.sight !==
cur?.sights[field?.name]?.sight
}
>
{() => {
const disabled = !form.getFieldValue([
"sights",
field.name,
"sight"
]);
return (
<Form.Item
{...field}
label="Price"
name={[field.name, "price"]}
rules={[{ required: true, message: "Missing price" }]}
>
<Input disabled={disabled} />
</Form.Item>
);
}}
</Form.Item>
<MinusCircleOutlined onClick={() => remove(field.name)} />
<Button
style={{ marginTop: "30px" }}
onClick={() => changeOrder(index, "Up")}
block
>
Up
</Button>
<Button
style={{ marginTop: "30px" }}
onClick={() => changeOrder(index, "Down")}
block
>
Down
</Button>
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
>
Add sights
</Button>
</Form.Item>
</>
)}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default App;
Antd Form List provides move
function to sort items inside a list. You can use that to sort items. move
function requires two arguments. from
index and to
index.
Here's the complete code.
import React from 'react';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Select, Space } from 'antd';
const { Option } = Select;
const areas = [
{ label: 'Beijing', value: 'Beijing' },
{ label: 'Shanghai', value: 'Shanghai' }
];
const sights = {
Beijing: ['Tiananmen', 'Great Wall'],
Shanghai: ['Oriental Pearl', 'The Bund']
};
type SightsKeys = keyof typeof sights;
const App: React.FC = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log('Received values of form:', values);
};
const handleChange = () => {
form.setFieldsValue({ sights: [] });
};
return (
<Form form={form} name='dynamic_form_complex' onFinish={onFinish} style={{ maxWidth: 600 }} autoComplete='off'>
<Form.Item name='area' label='Area' rules={[{ required: true, message: 'Missing area' }]}>
<Select options={areas} onChange={handleChange} />
</Form.Item>
<Form.List name='sights'>
{(fields, { add, remove, move }) => (
<>
{fields.map((field, index) => (
<Space key={field.key} align='baseline'>
<Form.Item
noStyle
shouldUpdate={(prevValues, curValues) =>
prevValues.area !== curValues.area || prevValues.sights !== curValues.sights
}
>
{() => (
<Form.Item
{...field}
label={`Sight ${field.name + 1}`}
name={[field.name, 'sight']}
rules={[{ required: true, message: 'Missing sight' }]}
>
<Select disabled={!form.getFieldValue('area')} style={{ width: 130 }}>
{(sights[form.getFieldValue('area') as SightsKeys] || []).map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</Form.Item>
)}
</Form.Item>
<Form.Item noStyle shouldUpdate={(prev, cur) => prev?.sights[field?.name]?.sight !== cur?.sights[field?.name]?.sight}>
{() => {
const disabled = !form.getFieldValue(['sights', field.name, 'sight']);
return (
<Form.Item
{...field}
label='Price'
name={[field.name, 'price']}
rules={[{ required: true, message: 'Missing price' }]}
>
<Input disabled={disabled} />
</Form.Item>
);
}}
</Form.Item>
<MinusCircleOutlined onClick={() => remove(field.name)} />
<Button style={{ marginTop: '30px' }} onClick={() => move(index, index - 1)} block disabled={index === 0}>
Up
</Button>
<Button
style={{ marginTop: '30px' }}
onClick={() => move(index, index + 1)}
block
disabled={index === fields.length - 1}
>
Down
</Button>
</Space>
))}
<Form.Item>
<Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
Add sights
</Button>
</Form.Item>
</>
)}
</Form.List>
<Form.Item>
<Button type='primary' htmlType='submit'>
Submit
</Button>
</Form.Item>
</Form>
);
};
export default App;
TLDR:
Each item has two buttons and you cannot UP
operation to first item as there's no item before it. Same for last item (No Down operation).
You can either disable the buttons or hide it based on the list length and the item index. The above code disable the button according the item position.