Tom
Tom

Reputation: 221

How to create dynamic form input fields in React with ANTd

Codesandbox link: https://codesandbox.io/s/compassionate-sanderson-1m8nv?file=/src/App.js

I have not been able to find much information on this topic. The following is what I want to achieve:

I want the user to be able to edit some details of a purchase order that already exists in the database, then resubmit the purchase order with a form. The details of the original purchase order should be displayed in the form input fields, and the user can change them directly via those field and then submit.

I am not savvy with web development so please bear with me.

I want the final form object to look like this:

{
    po_number:"123abc",
    carrier:"Fastway",
    items: [{
                item_code:"dnh75n",
                quantity:"10",
                special_requirements:"Add picture of happy dog"
            },
            {
                item_code:"456def",
                quantity:"4",
                special_requirements:"Do not include lids"
            }
        ]
}

The number of form input fields generated will be based off how many items are in the purchase order. I have created a simple React component below to demonstrate what I am trying to do. Or just checkout out the code sandbox link above. Any help would be appreciated. I can't even find information on how to group ANTd form items to create the array of items in the purchase order. I've seen plenty of dynamic form examples on their website but I want to create the form based on the preexisting items in the purchase order, not add fields with user input.

import { Form, Input, Button } from 'antd';

//This is the order that already exists
const order = {
    po_number:"123abc",
    carrier:"Fastway",
    items: [{
                item_code:"dnh75n",
                quantity:"10",
                special_requirements:"Add picture of happy dog"
            },
            {
                item_code:"456def",
                quantity:"4",
                special_requirements:"Do not include lids"
            }
        ]
};


const GroupForm = () => {

    const onFinish = values => {
        console.log(values);
    }
    

    //Create form fields based off how many items are in the order
    const itemInputs = order.items.map(item => {
        return (
            <div>
                <b>Item{" " + item.item_code}</b>
                <Form.Item name={item.item_code + "_quantity"} label="quantity">
                    <Input defaultValue={item.quantity} style={{width: "500px"}} />
                </Form.Item>

                <Form.Item name={item.item_code + "_requirements"} label="speacial requirements">
                    <Input defaultValue={item.special_requirements}  style={{width: "500px"}} />
                </Form.Item>
            </div>
        );
    });

    return(
        <div>
            <Form onFinish={onFinish}>
                <b>{"Order " + order.po_number}</b>
                
                <Form.Item name="carrier" label="carrier">
                    <Input defaultValue={order.carrier} style={{width: "500px"}} />
                </Form.Item>

                <b>Order Items</b>

                {itemInputs}

                <Form.Item>
                    <Button type="primary" htmlType="submit"> Change Details </Button>
                </Form.Item>
            </Form>
        </div>
    );
}

export default GroupForm;

Upvotes: 6

Views: 20881

Answers (2)

Scratch&#39;N&#39;Purr
Scratch&#39;N&#39;Purr

Reputation: 10399

If you're using antd version 4.9.0+, you can take advantage of the initialValue property on the Form.List. This allows you set intial values on the form items of the array. Alternatively, you can set the initialValues property on the Form. Here's a minimum viable example using the former method.

import { Form, Input, Button, Space } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
import React from "react";

//Basic Idea
/* 
    I want the user to be able to edit some details of a purchase order that already exists in the database,
    then resubmit the order with a form.
    The details of the purchase order should be orginally displayed in the form input fields,
    and the user can change them directly via those fields. 
*/

//This is the order that already exists
const order = {
  po_number: "123abc",
  carrier: "Fastway",
  items: [
    {
      item_code: "dnh75n",
      quantity: "10",
      special_requirements: "Add picture of happy dog"
    },
    {
      item_code: "456def",
      quantity: "4",
      special_requirements: "Do not include lids"
    }
  ]
};

const itemInputs = order.items

const GroupForm = () => {
  const onFinish = (values) => {
    console.log(values);
  };

  /**
   * Edited: `const itemInputs = order.items` above
   */
  //Create form fields based off how many items are in the order
  // const itemInputs = order.items.map((item) => {
  //   return {
  //     item_code: item.item_code,
  //     quantity: item.quantity,
  //     special_requirements: item.special_requirements
  //   };
  // });

  return (
    <div>
      <Form onFinish={onFinish}>
        <b>{"Order " + order.po_number}</b>

        <Form.Item name="carrier" label="carrier" initialValue={order.carrier}>
          <Input style={{ width: "500px" }} />
        </Form.Item>
        <Form.Item
          name="po_number"
          label="PO number"
          initialValue={order.po_number}
          hidden
        >
          <Input />
        </Form.Item>

        <b>Order Items</b>

        <Form.List name="items" initialValue={itemInputs}>
          {(fields, { add, remove }) => (
            <>
              {fields.map((field) => (
                <Space
                  key={field.key}
                  style={{ display: "flex", marginBottom: 8 }}
                  align="baseline"
                >
                  <Form.Item
                    {...field}
                    name={[field.name, "item_code"]}
                    // no need anymore
                    // fieldKey={[field.fieldKey, "item_code"]}
                  >
                    <Input placeholder="Item Code" />
                  </Form.Item>
                  <Form.Item
                    {...field}
                    name={[field.name, "quantity"]}
                    // no need anymore
                    // fieldKey={[field.fieldKey, "quantity"]}
                  >
                    <Input placeholder="Quantity" />
                  </Form.Item>
                  <Form.Item
                    {...field}
                    name={[field.name, "special_requirements"]}
                    // no need anymore
                    // fieldKey={[field.fieldKey, "special_requirements"]}
                  >
                    <Input placeholder="Quantity" />
                  </Form.Item>
                  <MinusCircleOutlined onClick={() => remove(field.name)} />
                </Space>
              ))}
              <Form.Item>
                <Button
                  type="dashed"
                  onClick={add}
                  block
                  icon={<PlusOutlined />}
                >
                  Add item
                </Button>
              </Form.Item>
            </>
          )}
        </Form.List>

        <Form.Item>
          <Button type="primary" htmlType="submit">
            {" "}
            Change Details{" "}
          </Button>
        </Form.Item>
      </Form>
    </div>
  );
};

export default GroupForm;

// I want to submit a form object that looks like this. E.g.
// This is what 'onFinish' should display in the console
/*

{
    po_number:"123abc",
    carrier:"Fastway",
    items: [{
                item_code:"dnh75n",
                quantity:"10",
                special_requirements:"Add picture of happy dog"
            },
            {
                item_code:"456def",
                quantity:"4",
                special_requirements:"Do not include lids"
            }
        ]
}

*/

DEMO

Upvotes: 9

MS1
MS1

Reputation: 518

Thanks to Scratch'N'Purr, I used your solution to further put the fields into an antd table.

First a short note: fieldKey={[field.fieldKey, "quantity"]} from the previous solution didn't work for me, but changing it to key={[field.key, "quantity"]}did the trick.

//define the columns for the table
const columns = [
    {
      title: "ITEM#",
      dataIndex: "item_code",
      key: "item_code",
      width: "12%",
      //use the field here to get all infos for the form
      render: (_, field) => (
        <Form.Item
          {...field}
          name={[field.name, "item_code"]}
          key={[field.key, "item_code"]}
          noStyle
        >
          <Input placeholder="ITEM#" />
        </Form.Item>
      ),
    },
    {
      title: "Quantity",
      dataIndex: "quantity",
      key: "quantity",
      render: (_, field) => (
        <Form.Item
          {...field}
          name={[field.name, "quantity"]}
          //@ts-ignore
          key={[field.key, "quantity"]}
          noStyle
        >
          <Input placeholder="Quantity" />
        </Form.Item>
      ),
}];

const GroupForm = () => {
  const onFinish = (values) => {
    console.log(values);
  };

  //Create form fields based off how many items are in the order
  const itemInputs = order.items.map((item) => {
    return {
      item_code: item.item_code,
      quantity: item.quantity,
      special_requirements: item.special_requirements,
    };
  });

  return (
    <div>
      <Form onFinish={onFinish}>
        <b>{"Order " + order.po_number}</b>
        <Form.Item name="carrier" label="carrier" initialValue={order.carrier}>
          <Input style={{ width: "500px" }} />
        </Form.Item>
        <Form.Item
          name="po_number"
          label="PO number"
          initialValue={order.po_number}
          hidden
        >
          <Input />
        </Form.Item>

        <b>Order Items</b>

        <Form.List name="items" initialValue={itemInputs}>
          {(fields, { add, remove }, { errors }) => (
            <>
              {/* This is where to put the table. As a data source i used the fields */}
              <Table dataSource={fields} columns={columns} pagination={false} />
              <Form.Item>
                <Button
                  type="dashed"
                  onClick={() => add()}
                  style={{ width: "60%" }}
                  icon={<PlusOutlined />}
                >
                  Add field
                </Button>
                <Form.ErrorList errors={errors} />
              </Form.Item>
            </>
          )}
        </Form.List>

        <Form.Item>
          <Button type="primary" htmlType="submit">
            {" "}
            Change Details{" "}
          </Button>
        </Form.Item>
      </Form>
    </div>
  );
};


Upvotes: 2

Related Questions