From HOC to Hooks: How Ant Design Form Works Under the Hood

This article explores the inner workings of Ant Design Form, comparing the legacy HOC‑based implementation with the modern Hooks API, detailing state‑lifting, form store design, performance trade‑offs, and providing step‑by‑step code examples for building a custom form solution in React.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
From HOC to Hooks: How Ant Design Form Works Under the Hood

Ant Design Form Overview

As a front‑end developer, forms are the most frequently used component, and Ant Design Form is the go‑to library for React developers. From simple login dialogs to complex data‑entry pages, it appears everywhere. This article dives into its implementation, tracing the evolution from the HOC‑based Form.create() API to the Hooks‑based Form.useForm() API.

3.x Version – HOC Pattern

Design Idea

In version 3.x, Ant Design Form uses a state‑lifting strategy: form controls and the submit button share a common ancestor that stores shared state (values and errors). This enables cross‑component state sharing, unified validation logic, and centralized data flow.

Cross‑component state sharing

Unified validation logic

Centralized data flow

HOC Pattern Explained

The core implementation is a Higher‑Order Component (HOC). It is a function that takes a component and returns a new component, achieving logic decoupling, reusability, and state isolation.

Logic decoupling : separates form logic from UI rendering

Reusability : the same form logic can be reused across components

State isolation : each form maintains its own context

Practical Demo – Simplified HOC Form

const nameRules = { required: true, message: "请输入用户名!" };
const passwordRules = { required: true, message: "请输入密码!" };

@Form.create()
class MyForm extends Component {
  componentDidMount() {
    this.props.form.setFieldsValue({ username: "小明" });
  }
  submit = () => {
    const { getFieldsValue, validateFields } = this.props.form;
    console.log("submit", getFieldsValue());
    validateFields((err, val) => {
      if (err) console.log("err", err);
      else console.log("校验成功", val);
    });
  };
  render() {
    const { getFieldDecorator } = this.props.form;
    return (
      <div>
        <h3>MyForm</h3>
        {getFieldDecorator("username", { rules: [nameRules] })(<Input placeholder="Username" />)}
        {getFieldDecorator("password", { rules: [passwordRules] })(<Input placeholder="Password" />)}
        <button onClick={this.submit}>submit</button>
      </div>
    );
  }
}
export default MyForm;

Hand‑written Form.create()

To replace the built‑in API, we can implement our own HOC:

import React, { Component } from "react";

export default function createForm(WrappedComponent) {
  return class extends Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

The HOC stores form state in its own state object and injects three key props into wrapped inputs via React.cloneElement:

name : field identifier

value : value read from HOC state

onChange : unified change handler

handleChange = e => {
  this.setState({ [e.target.name]: e.target.value });
};

getFieldDecorator = (field, options) => InputComponent => {
  return React.cloneElement(InputComponent, {
    name: field,
    value: this.state[field],
    onChange: this.handleChange,
  });
};

Validation iterates over stored field options and reports errors:

validateFields = callback => {
  let error = {};
  for (let field in this.options) {
    if (!this.state[field]) {
      error[field] = this.options[field].required;
    }
  }
  if (Object.keys(error).length > 0) callback(error, this.state);
  else callback(null, this.state);
};

Performance Drawbacks of HOC

Because all field values live in a single state object, any change triggers a re‑render of the entire HOC tree, causing even untouched fields to re‑render. This defeats component‑level optimizations such as React.memo or PureComponent, and deepens the component hierarchy, making debugging harder.

4.x/5.x Version – Hooks API

Design Idea

The Hooks implementation moves state management out of the component tree into an independent FormStore that follows a publish‑subscribe pattern, similar to Redux. The store holds field values and a list of subscribed items, notifying them only when their specific data changes.

FormStore architecture diagram
FormStore architecture diagram

Practical Demo – Simplified Hooks Form

import React, { Component } from "react";
import { Form, Button, Input } from "antd";

const FormItem = Form.Item;
const nameRules = { required: true, message: "请输入姓名!" };
const passworRules = { required: true, message: "请输入密码!" };

export default function MyForm(props) {
  const [form] = Form.useForm();
  const onFinish = val => console.log("onFinish", val);
  const onFinishFailed = val => console.log("onFinishFailed", val);
  return (
    <div>
      <h3>AntdFormPage</h3>
      <Form ref={formRef} onFinish={onFinish} onFinishFailed={onFinishFailed}>
        <FormItem rules={[nameRules]}>
          <Input placeholder="username placeholder" />
        </FormItem>
        <FormItem rules={[passworRules]}>
          <Input placeholder="password placeholder" />
        </FormItem>
        <FormItem>
          <Button type="primary" size="large" htmlType="submit">Submit</Button>
        </FormItem>
      </Form>
    </div>
  );
}

Architecture Layout

/my-form
├── FormContext.js    // context for passing form instance
├── Form.js          // form container component
├── Item.js          // Form.Item component
├── useForm.js       // form store logic
└── index.js         // export entry

FormContext

import { createContext } from "react";
const FormContext = createContext();
export default FormContext;

Form Component

import React from "react";
import FormContext from "./FormContext";
import useForm from "./useForm";

export default function Form({ children, form }) {
  const [formInstance] = useForm(form);
  return (
    <form>
      <FormContext.Provider value={formInstance}>
        {children}
      </FormContext.Provider>
    </form>
  );
}

Item Component (Controlled Input)

import React, { useContext } from "react";
import FormContext from "./FormContext";

export default function Item({ children, name }) {
  const formInstance = useContext(FormContext);
  const { getFieldValue, setFieldsValue } = formInstance;
  const getControlled = () => ({
    value: getFieldValue(name),
    onChange: e => {
      const newValue = e.target.value;
      setFieldsValue({ [name]: newValue });
    },
  });
  return React.cloneElement(children, getControlled());
}

FormStore (useForm Hook)

class FormStore {
  constructor() {
    this.store = {};
    this.itemEntities = [];
  }
  registerItemEntities = entity => {
    this.itemEntities.push(entity);
    return () => {
      this.itemEntities = this.itemEntities.filter(item => item !== entity);
      delete this.store[entity.props.name];
    };
  };
  getFieldsValue = () => ({ ...this.store });
  getFieldValue = name => this.store[name];
  setFieldsValue = newStore => {
    this.store = { ...this.store, ...newStore };
    this.itemEntities.forEach(entity => {
      if (Object.keys(newStore).includes(entity.props.name)) {
        entity.onStoreChange();
      }
    });
  };
  validateFields = callback => {
    const error = {};
    this.itemEntities.forEach(entity => {
      const { name, rules } = entity.props;
      const value = this.getFieldValue(name);
      const rule = rules && rules[0];
      if (rule && rule.required && (value === undefined || value === "")) {
        error[name] = rule.message;
      }
    });
    if (Object.keys(error).length) callback(error, this.store);
    else callback(null, this.store);
  };
  getForm = () => ({
    getFieldsValue: this.getFieldsValue,
    getFieldValue: this.getFieldValue,
    setFieldsValue: this.setFieldsValue,
    validateFields: this.validateFields,
    registerItemEntities: this.registerItemEntities,
  });
}

export default function useForm(form) {
  const formRef = React.useRef();
  if (!formRef.current) {
    const formStore = new FormStore();
    formRef.current = formStore.getForm();
  }
  return [formRef.current];
}

Form Submission

export default function Form({ children, form, onFinish, onFinishFailed }, ref) {
  const [formInstance] = useForm(form);
  formInstance.setCallbacks({ onFinish, onFinishFailed });
  return (
    <form onSubmit={e => { e.preventDefault(); formInstance.submit(); }}>
      <FormContext.Provider value={formInstance}>
        {children}
      </FormContext.Provider>
    </form>
  );
}

Conclusion

Both HOC and Hooks implementations embody valuable design insights. While HOC is gradually replaced by Hooks in modern React, understanding its decorator pattern deepens comprehension of state‑management libraries such as Redux or Zustand, and explains why React 18 introduced useSyncExternalStore for concurrent rendering scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

State ManagementReacthooksAnt DesignFormHOC
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

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.