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:
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:
How can I fix this?
I tried to use form with Form.List with the same results.
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;
};