Search code examples
javascriptreactjsantd

Re-order dynamic antd form fields


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;

Solution

  • 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.