Antd Forms, get values from custom component? - reactjs

I'm trying to add some custom components within getFieldDecorator and obtain the values onCreate added. Not sure how I can go about it since the state is found within the Custom components. Ideally, the custom component will handle all user input value but unsure about how to pass those values as part of the object onCreate.
import React from "react";
import { Icon, Modal, Form, Input } from "antd";
import Tags from "./EditableTagGroup";
const Taskform = Form.create({ name: "event_form" })(props => {
const { visible, onCancel, onCreate, form } = props;
const { getFieldDecorator } = form;
const handleCreate = () => {
form.validateFields((err, values) => {
if (err) {
return;
}
form.resetFields();
onCreate(values);
});
};
return (
<Modal
visible={visible}
title="Task"
closeIcon={
<Icon
type="close"
style={{ fontSize: "14px", fontWeight: "600", marginTop: "30px" }}
/>
}
okText="Create"
onCancel={onCancel}
onOk={handleCreate}
>
<Form layout="vertical">
<Form.Item label="Name">
{getFieldDecorator("name", {
rules: [
{
required: true,
message: "Please type the name of task!"
}
]
})(<Input placeholder="Write a task name" />)}
</Form.Item>
<Form.Item label="Description">
{getFieldDecorator("description")(
<Input.TextArea
style={{ minHeight: "60px" }}
autoSize={{ minRows: 3, maxRows: 6 }}
placeholder="Write a description"
/>
)}
</Form.Item>
<Form.Item>{getFieldDecorator("tags")(<Tags />)}</Form.Item>
</Form>
</Modal>
);
});
export default Taskform;

I have checked your code on sandbox. You may need to pass the props getFieldDecorator down to the EditableFormTag.js like below:
Step one: from the taskform.js
<Form.Item><Tags getFieldDecorator={getFieldDecorator} /></Form.Item>
Step two: Inside the EditableTagGroup.js
const { getFieldDecorator } = this.props;
{inputVisible &&
<Input
ref={this.saveInputRef}
onChange={this.handleInputChange}
onPressEnter={this.handleInputConfirm}
value={inputValue}
onBlur={this.handleInputConfirm}
type="text"
size="small"
style={{ width: 78 }}
/>
}
{getFieldDecorator("tags", {
initialValue: { tags }
})(
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ display: "none" }}
/>
)
}
End result

Related

How to stop modal from closing when clicking on a select option?

I've made a custom filter for MUI's datagrid, the filter has two select's which allow you to filter by the column and filter type. The selects are quite big and endup outside the modal, when clicking on an option the whole modal closes, how can I prevent this from happening?
I've used this tutorial - Detect click outside React component to detect clicks outside the filter.
The code below shows the filter and I've also made an codesandbox example here - https://codesandbox.io/s/awesome-panka-g92vhn?file=/src/DataGridCustomFilter.js:0-6708
any help would be appreciated
import React, { useState, useEffect, useRef } from "react";
import {
Button,
Stack,
FormControl,
InputLabel,
Select,
MenuItem,
Paper,
Grid,
IconButton,
TextField,
ClickAwayListener
} from "#material-ui/core";
import FilterListIcon from "#mui/icons-material/FilterList";
import AddIcon from "#mui/icons-material/Add";
import CloseIcon from "#mui/icons-material/Close";
import { useForm, useFieldArray, Controller } from "react-hook-form";
import { columns } from "./columns";
const filterTypes = {
string: ["contains", "equals", "starts with", "ends with", "is any of"],
int: ["contains", "equals", "less than", "greater than"]
};
function FilterRow({
len,
setOpen,
field,
control,
columns,
index,
handleRemoveFilter
}) {
return (
<Grid container spacing={0}>
<Grid
item
md={1}
style={{
display: "flex",
alignSelf: "flex-end",
alignItems: "center"
}}
>
<IconButton
size="small"
onClick={() => {
if (len === 1) {
setOpen(false);
} else {
console.log(index, "---");
handleRemoveFilter(index);
}
}}
>
<CloseIcon style={{ fontSize: "20px" }} />
</IconButton>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.column`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Column</InputLabel>
<Select
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<MenuItem value={a.headerName}>{a.headerName}</MenuItem>
);
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={3}>
<Controller
name={`filterForm.${index}.filter`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Filter</InputLabel>
<Select
value={value}
onChange={onChange}
label="Filter"
defaultValue=""
>
{filterTypes.string.map((a) => {
return <MenuItem value={a}>{a}</MenuItem>;
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.value`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl>
<TextField
onChange={onChange}
value={value}
label="Value"
variant="standard"
/>
</FormControl>
)}
/>
{/* )} */}
</Grid>
</Grid>
);
}
function DataGridCustomFilter() {
const { control, handleSubmit } = useForm();
const { fields, append, remove } = useFieldArray({
control,
name: "filterForm"
});
const [open, setOpen] = useState(false);
const onSubmit = (data) => {};
useEffect(() => {
if (fields.length === 0) {
append({
column: "ID",
filter: filterTypes.string[0],
value: ""
});
}
}, [fields]);
const [clickedOutside, setClickedOutside] = useState(false);
const myRef = useRef();
const handleClickOutside = (e) => {
if (myRef.current && !myRef.current.contains(e.target)) {
setClickedOutside(true);
setOpen(!open);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
});
return (
<>
<Button
startIcon={<FilterListIcon />}
size="small"
onClick={() => {
setOpen(!open);
}}
// disabled={isDisabled}
>
FILTERS
</Button>
{open ? (
<div ref={myRef}>
<Paper
style={{
width: 550,
padding: 10,
zIndex: 1300,
position: "absolute",
inset: "0px auto auto 0px",
margin: 0,
display: "block"
// transform: "translate3d(160.556px, 252.222px, 0px)",
}}
variant="elevation"
elevation={5}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={0.5}>
<div style={{ maxHeight: 210, overflow: "scroll" }}>
{fields.map((field, index) => {
return (
<div style={{ paddingBottom: 5 }}>
<FilterRow
len={fields.length}
control={control}
setOpen={setOpen}
field={field}
columns={columns}
handleRemoveFilter={() => remove(index)}
{...{ control, index, field }}
// handleClickAway={handleClickAway}
/>
</div>
);
})}
</div>
<div style={{ marginTop: 10, paddingLeft: 40 }}>
<Stack direction="row" spacing={1}>
<Button size="small" startIcon={<AddIcon />}>
ADD FILTER
</Button>
<Button size="small" type="submit">
{fields.length > 1 ? "APPLY FILTERS" : "APPLY FILTER"}
</Button>
</Stack>
</div>
</Stack>
</form>
</Paper>
</div>
) : null}
</>
);
}
export default DataGridCustomFilter;
So far I've tried MUI's ClickAwayListener and the example above, both seem to give the same result
DataGrid component uses NativeSelect. I have checked your codesandbox and tried replacing Select to NativeSelect and MenuItem to Option. filter is working properly. below is sample code for update.
...
<NativeSelect
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<option value={a.headerName}>{a.headerName}</option >
);
})}
</NativeSelect>
...

Unexpected useState behaviour in ant design table

I have ant design form and table side by side,When I fill the form data and click on add entity button the data should be displayed in the table. Now the data does get rendered when I click on add entity but again when I fill the form and click on the add entity button the previous data gets disappeared, Now I know that I need to copy the previous data and I did that too but its not working.
My Problem:- The previous form state value which is rendered in the table gets disappeared when new form data is added
My query:- How and Where should I copy the previous object with the spread operator so that it does not get reset.
My code is
import { Card, Table } from "antd";
import { Form, Input, Button, Select, Space, AutoComplete } from "antd";
import { DeleteOutlined, PlusOutlined, SendOutlined } from "#ant-design/icons";
import { useState } from "react";
const ExampleComponent = (props) => {
// Destructuring props
const { intent_data, entity_data } = props;
const [dataSource, setDataSource] = useState([{}]);
// To handle the disable state of Select Entity Select DropDown
const [addentity,setAddEntity] = useState(false)
// Handler function passed to YES/NO Select Option
const addEntityHandler = (addEntity) => {
if(addEntity === 'no'){
setAddEntity(true)
}else{
setAddEntity(false)
}
}
const [form] = Form.useForm();
const onFinish = (values) => {
console.log(values);
form.resetFields();
const dataArr = [];
// Push values to array since dataSource takes array not an object
dataArr.push(values);
setDataSource(dataArr);
};
const columns = [
{
title: "Entity",
dataIndex: "entity_name",
key: "entity_name",
},
{
title: "Entity Value",
dataIndex: "entity_value",
key: "entity_value",
},
{
title: "Operation",
key: "operation",
render: (record: any) => (
<DeleteOutlined
style={{ color: "red" }}
onClick={() => console.log(record)}
/>
),
},
];
return (
<Card className="csi-project-card-0934">
<div className="example-layout">
<div style={{ flexBasis: "100%" }}>
<Form
form={form}
labelCol={{ span: 7 }}
wrapperCol={{ span: 10 }}
layout="horizontal"
colon={true}
onFinish={onFinish}
size="large"
>
{/* <h4>Create Example</h4> */}
<Form.Item
label="Select Intent"
name="intent_name"
className="csi-ant-form-item"
rules={[{ required: true, message: "Intent Cannot be Empty!" }]}
>
<Select>
{/* {intent_data?.map?.((value) => (
<Select.Option
key={value.intent_ID}
value={value.intent_name}
>
{value.intent_name}
</Select.Option>
))} */}
<Select.Option value="intent demo">Intent Demo</Select.Option>
<Select.Option value="intent test">Intent Test</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Enter Example"
name="example_name"
className="csi-ant-form-item"
hasFeedback
rules={[
{ required: true, message: "This Field Cannot be Empty!" },
({ getFieldValue }) => ({
validator(_, value) {
if (value.length < 4) {
return Promise.reject("Length too short");
}
return Promise.resolve();
},
}),
]}
>
<AutoComplete>
<Input allowClear/>
</AutoComplete>
</Form.Item>
<Form.Item
label="Do you want to add Entity"
name="add_entity"
className="csi-ant-form-item"
rules={[{ required: true, message: "This Cannot be Empty!" }]}
>
<Select placeholder="SELECT" onSelect={(addEntity) => addEntityHandler(addEntity)}>
<Select.Option value="yes">YES</Select.Option>
<Select.Option value="no">NO</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Select Entity"
name="entity_name"
className="csi-ant-form-item"
>
<Select disabled = {addentity}>
<Select.Option value="entity demo">Entity Demo</Select.Option>
<Select.Option value="entity test">Entity Test</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Select Value"
name="entity_value"
className="csi-ant-form-item"
hasFeedback
rules={[
{ required: true, message: "This Field Cannot be Empty!" },
]}
>
<AutoComplete>
<Input placeholder="Select Value from Example" />
</AutoComplete>
</Form.Item>
<Form.Item className="csi-ant-form-item">
<Button
key="submit"
type="primary"
htmlType="submit"
shape="round"
>
Add Entity <PlusOutlined />
</Button>
</Form.Item>
</Form>
</div>
<div
style={{
flexBasis: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Table
bordered
className="ib-table"
dataSource={dataSource}
columns={columns}
pagination={{ pageSize: 6 }}
rowKey={Math.random().toString()}
/>
<Button key="submit" type="primary" htmlType="submit" shape="round">
Submit <SendOutlined />
</Button>
</div>
</div>
</Card>
);
};
export default ExampleComponent;
The form data is stored in values object and the structure of values is
{
add_entity: "yes"
entity_name: "entity demo"
entity_value: "Test"
example_name: "Test"
intent_name: "intent demo"
}
One thing to note here is that dataSource state variable is array of objects, like
[{
add_entity: "yes"
entity_name: "entity demo"
entity_value: "Test"
example_name: "Test"
intent_name: "intent demo"
}]
My Expected Output is Below
Entity
Entity Value
Operation
entity demo
test
delete icon
intent demo
test
delete icon
What if you try with
setDataSource(prevDataSource => [...prevDataSource, values]);
?
In the same way, to delete an item:
{
title: "Operation",
key: "operation",
render: (record) => (
<DeleteOutlined
style={{ color: "red" }}
onClick={() => {
setDataSource(prevDataSource => prevDataSource.filter(item => item.entity_name !== record.entity_name //entity_name or whatever id the item has ))
}}
/>
),
},
By the way, try to avoid using any whenever possible if you're using typescript. Here is how to:
import { Table } from 'antd';
import { ColumnsType } from 'antd/es/table';
interface User {
key: number;
name: string;
}
const columns: ColumnsType<User> = [
{
key: 'name',
title: 'Name',
dataIndex: 'name',
},
];
const data: User[] = [
{
key: 0,
name: 'Jack',
},
];
export default () => (
<>
<Table<User> columns={columns} dataSource={data} />
/* JSX style usage */
<Table<User> dataSource={data}>
<Table.Column<User> key="name" title="Name" dataIndex="name" />
</Table>
</>
);

How can I call scrollIntoView on an ref in React

When I call someFun, I want to make formRef visible on the screen(it is on the top of the page, I may have scrolled to the bottom when I call someFun), is there standart way to do that?
function List(props) {
const formRef = useRef(null);
const someFun = params => {
if (formRef && formRef.current) {
//error FormRef.current.scrollIntoView is not a function
formRef.current.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'start' });
}
}
}
This is the form component used in the above parent component:
it is a ant design form component:
the link is here
https://3x.ant.design/components/form/#header
import { Form, Icon, Input, Button, Checkbox } from 'antd';
class NormalLoginForm extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(<Checkbox>Remember me</Checkbox>)}
<a className="login-form-forgot" href="">
Forgot password
</a>
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
Or register now!
</Form.Item>
</Form>
);
}
}
const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(NormalLoginForm);
ReactDOM.render(<WrappedNormalLoginForm />, mountNode);
This how you can pass the ref from your NormalLoginForm to your List component.
In your List component you need to use forwardRef because it gives the child component a reference to a DOM element created by its parent component
Example
App
import "./styles.css";
import List from "./List";
import { useRef } from "react";
export default function App() {
const targetElement = useRef();
const scrollingTop = (event) => {
const elmnt = targetElement;
elmnt.current.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "start"
});
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<p>lorem ipsum </p>
<div style={{ height: "200vh", backgroundColor: "orange" }}>
<h1>Example Form Tag </h1>
<button id="btnAppear" onClick={scrollingTop}>
Submit Scroll bottom
</button>
</div>
<List ref={targetElement} />
</div>
);
}
List
import { forwardRef } from "react";
const List = (ref) => {
return (
<div
style={{
backgroundColor: "purple",
paddingTop: "50px",
color: "white"
}}
ref={ref}
>
<h1>List View</h1>
</div>
);
};
export default forwardRef(List);
DEMO

React and Antd, form data destroys on fulllscreen drawer out of focus

I have tried to remove the destroy on close, use same form hook, different form hook, removed from hook, but nothing worked.
I have consoled logged the form data on show modal and close modal. Show modal has the form data but inside the log of close modal the data is reset to undefined. As soon as the modal opens the Data resets to null.
In the following code you can see the creation of Full screen modal which is made using AntDrawer:
import React, { useEffect, useState } from "react";
import FullscreenDialog from "Components/FullScreenDialog";
import Form from "Components/Form";
import { Input, Typography, Button, Space, Menu, Dropdown } from "antd";
import { PlusCircleOutlined, CloseOutlined, DownOutlined } from "#ant-design/icons";
import HeaderInput from "Components/Inputs/HeaderInput";
import Table from "Components/Table";
import styled from "styled-components";
import { Services } from "Utilities/network";
import DetailsModal from "./DetailsModal";
const RootWrapper = styled.div`
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
`;
const CreateVoucher = ({ visible, initalValue = {}, onSuccess = (e) => e, onClose = (e) => e }) => {
const [form] = Form.useForm();
const [voucherData, setVoucherData] = useState([]);
const [newModal, setNewModal] = useState(false);
const [formData, setFormData] = useState({});
const handleSubmit = async () => {
const { _id, name, header } = form.getFieldsValue(true);
if (name && header && voucherData) {
let resp = await Services.createVoucher({
_id,
name,
header,
voucherData,
});
if (resp.key) onSuccess();
}
};
if (initalValue) {
const { _id, name, header, voucherData } = initalValue;
form.setFieldsValue({
_id,
name,
header,
voucherData,
});
}
useEffect(() => {
const { voucherData } = initalValue;
if (voucherData && voucherData.length) setVoucherData(voucherData);
else setVoucherData([]);
}, [initalValue]);
const handleDelete = (index) => {
const tableData = voucherData.filter((data, dataIndex) => {
if (index !== dataIndex) return data;
return null;
});
setVoucherData(tableData);
};
return (
<>
<FullscreenDialog visible={visible} onClose={onClose}>
<FullscreenDialog.Paper>
<RootWrapper>
<Typography.Text strong style={{ fontSize: "30px" }}>
Generate Voucher
</Typography.Text>
<Form form={form}>
<Space direction="vertical" style={{ width: "100%" }}>
<Form.Item label="Voucher ID" name="name" rules={[{ required: true, message: "Please input ID" }]}>
<Input />
</Form.Item>
<HeaderInput
fieldProps={{
name: "header",
label: "Inoivce Header",
rules: [{ required: true, message: "Please select header" }],
}}
itemProps={{ placeholder: "Select Header for Voucher" }}
/>
<Space style={{ float: "right", marginTop: "20px" }}>
<Button
type="primary"
shape="round"
icon={<PlusCircleOutlined />}
onClick={() => {
setFormData(form.getFieldsValue(true));
setNewModal(true);
}}
>
Add Details
</Button>
</Space>
<Table
dataSource={voucherData}
count={voucherData?.length || 0}
columns={[
{
title: "Label",
key: "label",
dataIndex: "label",
render: (_, record) => record.label,
},
{
title: "Details",
key: "detail",
dataIndex: "detail",
render: (_, record) => record.detail,
},
{
align: "right",
render: (_, record, index) => {
return (
<Dropdown
trigger={["click"]}
overlay={
<Menu>
<Menu.Item
style={{ color: "red " }}
onClick={() => {
handleDelete(index);
}}
>
<CloseOutlined /> Delete
</Menu.Item>
</Menu>
}
>
<Button type="primary">
Edit
<DownOutlined />
</Button>
</Dropdown>
);
},
},
]}
/>
<Space style={{ float: "right", marginTop: "30px" }}>
<Button type="outline" onClick={onClose}>
Cancel
</Button>
<Button type="primary" htmlType="submit" onClick={handleSubmit}>
Submit
</Button>
</Space>
</Space>
</Form>
</RootWrapper>
</FullscreenDialog.Paper>
</FullscreenDialog>
<DetailsModal
visible={newModal}
onClose={() => {
setNewModal(false);
console.log(formData);
form.setFieldsValue({ name: "2222" });
console.log(form.getFieldsValue(true));
}}
onSuccess={(data) => {
setVoucherData([...voucherData, data]);
setNewModal(false);
form.setFieldsValue(formData);
}}
/>
</>
);
};
export default CreateVoucher;
Below is the code for modal to use another form:
import React from "react";
import { Modal, Space, Input, Button } from "antd";
import Form from "Components/Form";
import styled from "styled-components";
const RootWrapper = styled.div`
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
`;
const DetailsModal = ({ visible, onClose = (e) => e, onSuccess = (e) => e }) => {
const [form] = Form.useForm();
return (
<Modal
visible={visible}
destroyOnClose={true}
onClose={() => {
form.resetFields();
onClose();
}}
onCancel={() => {
form.resetFields();
onClose(false);
}}
title="Add Details"
footer={false}
>
<Form form={form}>
<Space direction="vertical" style={{ width: "100%" }}>
<RootWrapper>
<Form.Item name="label" label="Label" rules={[{ required: true, message: "Please input label" }]}>
<Input />
</Form.Item>
<Form.Item name="detail" label="Details" rules={[{ required: true, message: "Please input details" }]}>
<Input />
</Form.Item>
</RootWrapper>
<Space style={{ float: "right", marginTop: "20px" }}>
<Button
type="outline"
onClick={() => {
form.resetFields();
onClose();
}}
>
Cancel
</Button>
<Button
type="primary"
htmlType="submit"
onClick={() => {
const { detail, label } = form.getFieldsValue(true);
onSuccess({ detail, label });
}}
>
Submit
</Button>
</Space>
</Space>
</Form>
</Modal>
);
};
export default DetailsModal;

Create nested form using react js

I use ant design in my application, where i use dynamic form. Users can add how many descendant forms they want.
<Form
name="dynamic_form_item"
{...formItemLayoutWithOutLabel}
onFinish={onFinish}
>
<Form.List name="names">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
{...(index === 0
? formItemLayout
: formItemLayoutWithOutLabel)}
label={index === 0 ? "Passengers" : ""}
required={false}
key={field.key}
>
<Form.Item
{...field}
validateTrigger={["onChange", "onBlur"]}
rules={[
{
required: true,
whitespace: true,
message:
"Please input passenger's name or delete this field."
}
]}
noStyle
>
<Input
placeholder="passenger name"
style={{ width: "60%" }}
/>
</Form.Item>
<InnerForm /> //my inner form
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
style={{ margin: "0 8px" }}
onClick={() => {
remove(field.name);
}}
/>
) : null}
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
The idea of the application is next:
User click on add field and appears an input where he can add a value, also appears another button with add inner field where user also can add another data.
Now the application works and when i save each form, i get the value, but also i get a warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>, which means that i can't use inner form inside the main how i understand. But how to solve this issue and to make disappears the warning?
demo: https://codesandbox.io/s/ecstatic-paper-0933k?file=/index.js:677-2823
SubForm.js. Removing the form tag gets rid of the warning. and also remove the inner submit button
import React from "react";
import { Form, Input, Button } from "antd";
import { PlusOutlined } from "#ant-design/icons";
const SubForm = props => {
return (
<>
<Form.List name={[props.fieldKey, "inner"]}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
// name={"aar"}
{...field}
name={[field.name, "innerName"]}
fieldKey={[field.fieldKey, "innerName"]}
key={index}
noStyle
>
<Input
placeholder="passenger name"
style={{ width: "60%" }}
/>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</>
);
};
export default SubForm;
index.js
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Input, Button } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "#ant-design/icons";
import InnerForm from "./SubForm";
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 }`
}
};`
const formItemLayoutWithOutLabel = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 20, offset: 4 }
}
};
const DynamicFieldSet = () => {
const onFinish = values => {
console.log("Received values of form:", values);
};
return (
<Form
name="dynamic_form_item"
{...formItemLayoutWithOutLabel}
onFinish={onFinish}
>
<Form.List name="names">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => {
console.log(field);
return (
<Form.Item
{...(index === 0
? formItemLayout
: formItemLayoutWithOutLabel)}
label={index === 0 ? "Passengers" : ""}
required={false}
key={field.key}
>
<Form.Item
{...field}
name={[field.name, "outer"]}
fieldKey={[field.fieldKey, "outer"]}
validateTrigger={["onChange", "onBlur"]}
rules={[
{
required: true,
whitespace: true,
message:
"Please input passenger's"
}
]}
noStyle
>
<Input
placeholder="passenger name"
style={{ width: "60%" }}
/>
</Form.Item>
<Form.Item>
<InnerForm fieldKey={field.key} />
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
style={{ margin: "0 8px" }}
onClick={() => {
remove(field.name);
}}
/>
) : null}
</Form.Item>
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<DynamicFieldSet />, document.getElementById("container"));

Resources