How to Evolve Ant Design Forms: Adding Dynamic Code Fields and Copy Functionality
This article walks through the iterative design of an Ant Design form component, starting with a simple name field, then adding a randomly generated code field, making it editable, and finally implementing a copyable code input with reusable prefix‑text and suffix‑icon components, while comparing implementation approaches.
Our component design is not a one‑off; it evolves with requirements to improve extensibility and reuse.
A Simple Form
First we implement a form with only a
namefield using Ant Design, omitting validation for simplicity. After the user fills the form, clicking Submit sends the data to the backend.
We use the Ant Design component library to implement this form:
<code>import { Form, Button, Input } from 'antd';
const BasicForm = () => {
const onFinish = (values) => {
submitToServer(values);
};
return (
<Form onFinish={onFinish}>
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
);
};</code>Add Randomly Generated code Field
As the business evolves, a new requirement adds a
codefield named
code, generated on the frontend and submitted together with
name. Its format must be
custom_${6‑digit‑random‑string}.
Two implementation approaches are considered:
Option 1: Add code on Submit
This approach processes the data at submit time, adding the
codefield before sending:
<code>const BasicForm = () => {
const onFinish = (values) => {
- submitToServer(values);
+ const data = {
+ ...values,
+ code: `custom_${uid().slice(0, 6)}`
+ };
+ submitToServer(data);
};
return (
<Form onFinish={onFinish}>
// ... ...
</Form>
);
};</code>Option 2: Add Hidden code Field
Here the
codefield is added to the form as a hidden item with an initial value:
<code>const BasicForm = () => {
// ... ...
return (
<Form onFinish={onFinish}>
// ... ...
+ <Form.Item label="Code" name="code" hidden initialValue={`custom_${uid().slice(0, 6)}`}>
+ <Input />
+ </Form.Item>
// ... ...
</Form>
);
};</code>Both approaches have similar cost; it is hard to declare a superior solution.
Editable code Field
The next requirement allows users to edit the part after
custom_in the
codefield.
If the form originally used Option 1, it now needs to switch to Option 2 to support editing.
Option 1: Process code on Submit
The form stores the user‑entered value and concatenates the prefix before submission:
<code>const BasicForm = () => {
const onFinish = (values) => {
const data = {
...values,
- code: `custom_${uid().slice(0, 6)}`
+ code: `custom_${values.code}`
};
submitToServer(data);
};
return (
<Form onFinish={onFinish}>
// ... ...
<Form.Item label="Code" name="code" initialValue={uid().slice(0, 6)}>
- <Input />
+ <Input addonBefore={'custom'} />
</Form.Item>
// ... ...
</Form>
);
};</code>Option 2: Process code Inside the Form
We create a custom input component that prefixes
custom_automatically:
<code>const InputWithPrefixCustom = (props) => {
const { onChange, value } = props;
const prefix = 'custom';
const handleChange = (event) => {
const val = event.target.value;
onChange && onChange(`${prefix}_${val}`);
};
const reg = new RegExp(`^${prefix}_`, 'g');
const valueWithoutPrefix = value.replace(reg, '');
return (
<Input value={value} addonBefore={prefix} onChange={handleChange} />
);
};</code>Using the component in the form:
<code>const BasicForm = () => {
const onFinish = (values) => {
submitToServer(values);
};
return (
<Form onFinish={onFinish}>
// ... ...
<Form.Item label="Code" name="code" initialValue={`custom_${uid().slice(0, 6)}`}>
- <Input />
+ <InputWithPrefixCustom />
</Form.Item>
// ... ...
</Form>
);
};</code>Option 1 is simpler, while Option 2 introduces a reusable component at the cost of added complexity.
Support Copy code Field
Later the requirement adds an icon after the
codeinput to copy its value.
More Generic Component Design
We abstract a lower‑level component that combines a prefix text, an input, and a suffix icon:
<code>const InputWithPrefixTextAndSuffixIcon = (props) => {
const { prefixText, value, suffixIcon, onChange } = props;
const handleChange = (event) => {
const val = event.target.value;
onChange && onChange(`${prefixText}_${val}`);
};
const reg = new RegExp(`^${prefixText}_`, "g");
const valueWithoutPrefix = value.replace(reg, "");
return (
<div style={{ display: "flex" }}>
<Input value={valueWithoutPrefix} addonBefore={prefixText} onChange={handleChange} />
{suffixIcon}
</div>
);
};</code>The
suffixIconcan be any icon component, e.g., a copy icon:
<code>const BasicForm = () => {
const [form] = useForm();
const onFinish = (values) => {
submitToServer(values);
};
const handleCopy = () => {
const code = form.getFieldValue("code");
navigator.clipboard.writeText(code);
};
const prefixText = 'custom';
return (
<Form form={form} onFinish={onFinish}>
// ... ...
<Form.Item label="Code" name="code" initialValue={`custom_${uid().slice(0, 6)}`}>
+ <InputWithPrefixTextAndSuffixIcon
+ prefixText={prefixText}
+ suffixIcon={<CopyOutlined style={{ paddingLeft: "8px" }} onClick={handleCopy} />}
+ />
</Form.Item>
// ... ...
</Form>
);
};</code>Because this generic component still requires custom copy logic, we create a dedicated component for the copy use‑case:
<code>const InputWithPrefixTextAndSuffixCopyIcon = (props) => {
const { value, prefixText, onChange } = props;
const handleCopy = () => {
navigator.clipboard.writeText(value);
};
return (
<InputWithPrefixTextAndSuffixIcon
+ prefixText={prefixText}
+ value={value}
+ onChange={onChange}
+ suffixIcon={<CopyOutlined style={{ paddingLeft: "8px" }} onClick={handleCopy} />}
/>
);
};</code>Using the copy‑specific component in the form:
<code>const BasicForm = () => {
const onFinish = (values) => {
submitToServer(values);
};
const prefixText = 'custom';
return (
<Form onFinish={onFinish}>
// ... ...
<Form.Item label="Code" name="code" initialValue={`${prefixText}_${uid().slice(0, 6)}`}>
+ <InputWithPrefixTextAndSuffixCopyIcon prefixText={prefixText} />
</Form.Item>
// ... ...
</Form>
);
};</code>Summary
The form implementation evolves as requirements change; it is difficult to predict future iterations at the outset. Therefore, we should avoid making assumptions about future needs unless they are certain.
Each solution is not unique; the best approach is the current optimal one. If it is not optimal, refactoring should be performed to make the implementation more reasonable and maintainable. There is no eternal design, only continuous refactoring.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.