Frontend Development 13 min read

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.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
How to Evolve Ant Design Forms: Adding Dynamic Code Fields and Copy Functionality

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

name

field 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

code

field 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

code

field 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

code

field 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

code

field.

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

code

input 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

suffixIcon

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

code generationReactcomponent designant-designFormCopy to Clipboard
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.