Search code examples
reactjsformsantd

How can I generate ant.d Form with multiple dynamic rows?


I'm trying to create Form using ReactJS and antd Form. Each row in the form needs to render a different element (Input / Select) based on the user selection in the first element in the row.

See image for example:

enter image description here

The first row works fine, the problem starts on the second row: when I change the value in "param" it changes the 3rd input element type in all the rows.

I added the code here:

https://codesandbox.io/p/sandbox/select-row-gcsfzj?file=%2Fsrc%2FApp.tsx%3A33%2C30-33%2C35

Steps to reproduce my problem:

  1. Add row
  2. Select param-1 value in "param" the 3rd element in the row is now "text input"
  3. Add new row
  4. Select param-2 value in "param", now the 3rd element type is "Select" but also all other rows have changed, and I need to change only the current row and keep all other rows as is.

How can I fix this?

I tried to use form with Form.List with the same results.


Solution

  • You are using same selectedParam state to handle multiple rows. So if update one param it will update all other rows.

    Second you are not using antd List properly. When iterating over fields array provide by Form.List, each field/item will have a key name of type number. You can call it as a index of that field. Since each row has 3 fields, you can assign a key to each field so it get store in form at that particular index like this <Form.Item name={[field.name, "param"]}>.... Note that you do not need to add Form.List name before the start of name={[field.name, "param"]} as it's already added by antd.

    Now based on each row selected param, you want to render a custom field like Input, Select.... I have created a component DynamicField that accepts the name as prop (you can say the index of that partuicular field/row...) and using Form.useWatch, i listen for particular Form value and render the field accordingly. Here you need to pass the complete name path.

    Here's the complete code.

    import { Button, Form, Input, Select, type SelectProps, Space } from 'antd';
    
    type FormType = { transactionFilter: Array<{ param: string; operator: string; value: string }> };
    
    const paramOptions: SelectProps['options'] = [
        { label: 'param-1', value: 'param-1' },
        { label: 'param-2', value: 'param-2' },
        { label: 'param-3', value: 'param-3' }
    ];
    
    const operatorOptions: SelectProps['options'] = [
        { label: 'operator-1', value: 'operator-1' },
        { label: 'operator-2', value: 'operator-2' }
    ];
    
    export default function App() {
        const [form] = Form.useForm<FormType>();
    
        const onFinish = (values: FormType) => {
            console.log('Received values of form:', values);
        };
    
        return (
            <Form onFinish={onFinish} className='my-form' form={form}>
                <Form.List name='transactionFilter'>
                    {(fields, { add }) => (
                        <div id='second-div' style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}>
                            {fields.map((field) => (
                                <div key={field.key} id='3rd-div'>
                                    <Space>
                                        <Form.Item label='param' name={[field.name, 'param']}>
                                            <Select style={{ width: 120 }} placeholder='params' options={paramOptions} />
                                        </Form.Item>
                                        <Form.Item label='operator' name={[field.name, 'operator']}>
                                            <Select style={{ width: 120 }} placeholder='operator' options={operatorOptions} />
                                        </Form.Item>
                                        <DynamicField name={field.name} />
                                    </Space>
                                </div>
                            ))}
    
                            <Button onClick={() => add()}>+ Add Row</Button>
                        </div>
                    )}
                </Form.List>
            </Form>
        );
    }
    
    const DynamicField = ({ name }: { name: number }) => {
        const param = Form.useWatch(['transactionFilter', name, 'param']);
    
        if (param === 'param-1') {
            return (
                <Form.Item label='text' name={[name, 'value']}>
                    <Input placeholder='value-text-input' />
                </Form.Item>
            );
        }
    
        if (param === 'param-2') {
            return (
                <Form.Item label='select' name={[name, 'value']}>
                    <Select
                        style={{ width: 150 }}
                        placeholder={'Select-input'}
                        options={[
                            { label: 'value-1', value: 'value-1' },
                            { label: 'value-2', value: 'value-2' }
                        ]}
                    />
                </Form.Item>
            );
        }
    
        return null;
    };