I used the React ant design drawer , When the user did not complete the form, I clicked on the outer side of the drawer and tried to display the notification message. but its not working, anyone know how to do that correctly
stack blitz here
Code here
import React, { Component } from 'react';
import { render } from 'react-dom';
import 'antd/dist/antd.css';
import './style.css';
import { Drawer, Form, Button, Col, Row, Input, Select, DatePicker, Icon } from 'antd';
const { Option } = Select;
class App extends Component {
state = { visible: false };
showDrawer = () => {
this.setState({
visible: true,
});
};
onClose = () => {
this.setState({
visible: false,
});
};
render() {
return (
<div>
<Button type="primary" onClick={this.showDrawer}>
<Icon type="plus" /> New account
</Button>
<Drawer
title="Create a new account"
width={720}
onClose={this.onClose}
visible={this.state.visible}
bodyStyle={{ paddingBottom: 80 }}
>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="Name">
<Input placeholder="Please enter user name" />)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="Url">
<Input
style={{ width: '100%' }}
addonBefore="http://"
addonAfter=".com"
placeholder="Please enter url"
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="Owner">
<Select placeholder="Please select an owner">
<Option value="xiao">Xiaoxiao Fu</Option>
<Option value="mao">Maomao Zhou</Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="Type">
<Select placeholder="Please choose the type">
<Option value="private">Private</Option>
<Option value="public">Public</Option>
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="Approver">
<Select placeholder="Please choose the approver">
<Option value="jack">Jack Ma</Option>
<Option value="tom">Tom Liu</Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="DateTime">
<DatePicker.RangePicker
style={{ width: '100%' }}
getPopupContainer={trigger => trigger.parentNode}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<Form.Item label="Description">
<Input.TextArea rows={4} placeholder="please enter url description" />)}
</Form.Item>
</Col>
</Row>
<div
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
}}
>
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
Cancel
</Button>
<Button onClick={this.onClose} type="primary">
Submit
</Button>
</div>
</Drawer>
</div>
);
}
}
render(<App />, document.getElementById('root'));
You need to add the logic about displaying notification on the onClose method of the <Drawer/>. Note that this method takes as input the elements that can trigger 'close' of drawer which are a div (background mask), an svg (X icon) and a button (Cancel).
At the following example the error notification is displayed if any input is empty when the drawer mask is clicked.
You can find also the example here: https://stackblitz.com/edit/react-28u4zw
import React, { Component } from "react";
import { render } from "react-dom";
import "antd/dist/antd.css";
import "./style.css";
import {
message,
Drawer,
Form,
Button,
Col,
Row,
Input,
Select,
DatePicker,
Icon
} from "antd";
const { Option } = Select;
const FIELD_NAMES = ["name", "url", "owner", "type", "approver", "dates"];
const initialValues = FIELD_NAMES.reduce(
(fieldList, fieldName) => ({ ...fieldList, [fieldName]: null }),
{}
);
class App extends Component {
state = {
visible: false,
...initialValues
};
showDrawer = () => {
this.setState({
visible: true
});
};
onClose = e => {
this.setState({
visible: false
});
const emptyFieldNames = FIELD_NAMES.filter(
fieldName => !this.state[fieldName]
);
if (emptyFieldNames.length > 0 && e.target.tagName === "DIV") {
return message.error(
`Please fill ${emptyFieldNames.join(", ")} field(s)`
);
}
};
setInput = fieldName => e => {
this.setState({ [fieldName]: e.target.value });
};
setSelect = fieldName => val => {
this.setState({ [fieldName]: val });
};
setDate = fieldName => dateList => {
this.setState({ [fieldName]: dateList.length > 0 ? dateList : null });
};
render() {
return (
<div>
<Button type="primary" onClick={this.showDrawer}>
<Icon type="plus" /> New account
</Button>
<Drawer
title="Create a new account"
width={720}
onClose={this.onClose}
visible={this.state.visible}
bodyStyle={{ paddingBottom: 80 }}
>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="Name">
<Input
placeholder="Please enter user name"
onChange={this.setInput("name")}
/>
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="Url">
<Input
style={{ width: "100%" }}
addonBefore="http://"
addonAfter=".com"
placeholder="Please enter url"
onChange={this.setInput("url")}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="Owner">
<Select
placeholder="Please select an owner"
onChange={this.setSelect("owner")}
>
<Option value="xiao">Xiaoxiao Fu</Option>
<Option value="mao">Maomao Zhou</Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="Type">
<Select
placeholder="Please choose the type"
onChange={this.setSelect("type")}
>
<Option value="private">Private</Option>
<Option value="public">Public</Option>
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="Approver">
<Select
placeholder="Please choose the approver"
onChange={this.setSelect("approver")}
>
<Option value="jack">Jack Ma</Option>
<Option value="tom">Tom Liu</Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="DateTime">
<DatePicker.RangePicker
style={{ width: "100%" }}
getPopupContainer={trigger => trigger.parentNode}
onChange={this.setDate("dates")}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<Form.Item label="Description">
<Input.TextArea
rows={4}
placeholder="please enter url description"
/>
)}
</Form.Item>
</Col>
</Row>
<div
style={{
position: "absolute",
right: 0,
bottom: 0,
width: "100%",
borderTop: "1px solid #e9e9e9",
padding: "10px 16px",
background: "#fff",
textAlign: "right"
}}
>
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
Cancel
</Button>
<Button onClick={this.onClose} type="primary">
Submit
</Button>
</div>
</Drawer>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Related
I see this link but didn't get any answer for my question
in my component , I have a two selectbox and one txtarea that owned Specified state(define by use state).
so on click add button this form create again and again,
my question is how to define new state dynamicly to set it for new selectbox that dynamicly created.
my select box use react-select and code of them is here :
<div className="mqContainer">
{[...Array(mqCount)].map((x, i) => (
<div key={i} className="newMqWrapper mt-4">
<Row className="mt-4">
<Col xs={3}>
<span>milestone</span>
</Col>
<Col xs={6}>
<Select
style={{ width: "100px !important" }}
defaultValue={mileStoneType}
onChange={setmileStoneType}
options={MileStoneTypeOption}
isMulti={true}
/>{" "}
</Col>
</Row>
<Row className="mt-4">
<Col xs={3}>
<span>question text</span>
</Col>
<Col xs={8}>
<Form.Group>
<Form.Control
placeholder="why so serious?"
className=" dirrtl"
as="textarea"
rows={3}
/>
</Form.Group>{" "}
</Col>
</Row>
<Row className="mt-4">
<Col xs={3}>
<span>answer type</span>
</Col>
<Col xs={6} className="mb-4">
<Select
defaultValue={mileStoneAnswerType}
onChange={setmileStoneAnswerType}
options={MqAnswerTypeOption}
/>{" "}
</Col>
</Row>
</div>
))}
<Row>
<Col xs={12} className=" mb-4 mt-4">
<Button
onClick={() => setMqCount(mqCount + 1)}
style={{ width: "100%" }}
variant="outline-secondary"
>
+
</Button>{" "}
</Col>
</Row>
</div>
I hope that I understand your problem correctly.
Regularly, you would create a Component in which you can call useState() separately. After that, you can render it inside your map().
I tried to reproduce your structure:
import { useState } from 'react'
const Form = () => {
const [ mqCount, setMqCount ] = useState(0)
return (
<div className='mqContainer'>
{[ ...Array(mqCount) ].map((x, i) => {
// TODO: pass your required props here
return <Item key={i} />
})}
<Row>
<Col className='mb-4 mt-4' xs={12}>
<Button
style={{ width: '100%' }}
variant='outline-secondary'
onClick={() => setMqCount(mqCount + 1)}
>
+
</Button>{' '}
</Col>
</Row>
</div>
)
}
const Item = ({ mileStoneType, MileStoneTypeOption, setmileStoneType }) => {
const [ yourState, setYourState ] = useState('Whatever you want')
// TODO: replace it with your own state
return (
<div key={i} className='newMqWrapper mt-4'>
<Row className='mt-4'>
<Col xs={3}>
<span>milestone</span>
</Col>
<Col xs={6}>
<Select
isMulti
defaultValue={mileStoneType}
options={MileStoneTypeOption}
style={{ width: '100px !important' }}
onChange={setmileStoneType}
/>{' '}
</Col>
</Row>
<Row className='mt-4'>
<Col xs={3}>
<span>question text</span>
</Col>
<Col xs={8}>
<Form.Group>
<Form.Control
as='textarea'
className=' dirrtl'
placeholder='why so serious?'
rows={3}
/>
</Form.Group>{' '}
</Col>
</Row>
<Row className='mt-4'>
<Col xs={3}>
<span>answer type</span>
</Col>
<Col className='mb-4' xs={6}>
<Select
defaultValue={mileStoneAnswerType}
options={MqAnswerTypeOption}
onChange={setmileStoneAnswerType}
/>{' '}
</Col>
</Row>
</div>
)
}
props.children is not working in my parent component, BoxCard. When I reference the child component directly instead of props.children, it works. Here's the code that's not working:
// AddUserForm.js
function AddUserForm(props) {
function handleSubmit(event) {
event.preventDefault();
}
return (
<BoxCard>
<Box component="form" onSubmit={handleSubmit}>
<TextField required id="name" label="Name" variant="filled" />
<TextField required type="number" id="age" label="Age" variant="filled" InputLabelProps={{ shrink: true }} InputProps={{inputProps: { min: 0, step: 1 }}}/>
<Button type="submit" variant="contained" startIcon={<FontAwesomeIcon icon={solid('circle-plus')}/>}>Add User</Button>
</Box>
</BoxCard>
)
}
export default AddUserForm;
// BoxCard.js
function BoxCard(props) {
return (
<Box sx={{ minWidth: 275 }}>
<Card variant="outlined">
<CardContent>
{props.children}
</CardContent>
</Card>
</Box>
)
}
export default BoxCard;
I'm using antd in react render a form with dynamic add and remove form.item.
In each form.item I'm trying to set an option which the day disable when it been selected in previous form.item.
So I was thinking about maybe I can use state that can set the day disable.
const [weekDays, setWeekDays] = useState({
Monday: false,
Tuesday: false,
Wednesday: false,
Thursday: false,
Friday: false,
Saturday: false,
Sunday: false,
});
And the form was like this
<Form.List name="classTime">
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Row key={field.key} align="baseline">
<Col span={8}>
<Form.Item
noStyle
shouldUpdate={(prevValues, curValues) =>
prevValues.area !== curValues.area ||
prevValues.sights !== curValues.sights
}
>
{() => (
<Form.Item
{...field}
name={[field.name, "weekday"]}
fieldKey={[field.fieldKey, "weekday"]}
rules={[
{ required: true, message: "Missing weekday" },
]}
>
<Select
disabled={false}
style={{ width: 130 }}
onChange={(value) => {
setWeekDays(value.true);
}}
>
{Object.entries(weekDays).map(([item,value]) => (
<Option
key={item}
value={item}
disabled={value}
>
{item}
</Option>
))}
</Select>
</Form.Item>
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
{...field}
name={[field.name, "time"]}
fieldKey={[field.fieldKey, "time"]}
// normalize={(value) => moment(value).format("HH:mm:ss")}
rules={[{ required: true, message: "Missing time" }]}
>
<TimePicker style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Col>
</Row>
))}
<Row>
<Col span={20}>
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
>
Add Class Time
</Button>
</Form.Item>
</Col>
</Row>
</>
)}
</Form.List>
it seems good but when I select a day will error me
Unhandled Runtime Error
TypeError: Cannot convert undefined or null to object
can't figure out why.
I made a customized file uploader button inside a pop-up box/dialog box. When I click the button the explorer will get opened but after choosing a file, onSubmit function gets called. And because of this file doesn't get selected properly. I want to stop calling the onSubmit automatically.
FileUploader.js
import React from 'react';
import * as Icon from 'react-bootstrap-icons';
const FileUploader = (props) => {
const hiddenFileInput = React.useRef(null);
const handleClick = (event) => {
console.log('Handle Click');
hiddenFileInput.current.click();
};
const handleChange = (event) => {
console.log('handle chnage');
const fileUploaded = event.target.files[0];
// props.handleFile(fileUploaded);
};
return (
<>
<button
style={{ border: 'none', background: 'white', paddingTop: -10 }}
onClick={handleClick}
>
<span>
<Icon.Paperclip />
</span>
</button>
<input
type="file"
ref={hiddenFileInput}
style={{ display: 'none' }}
onChange={handleChange}
/>
</>
);
};
export default FileUploader;
AddTicket.js
import React from "react";
import { Row, Col } from "reactstrap";
import { Form, FormGroup, Input } from "reactstrap";
import ActionButton from "./../../components/ButtonComponent";
import ReactQuill, { Quill } from "react-quill";
import "react-quill/dist/quill.snow.css";
import CustomToolbar from "./CustomToolbar";
import FileUploader from "./FileUploader";
class AddTicket extends React.Component {
constructor(props) {
super(props);
this.state = {
editorHtml: "",
description: "",
};
}
handleSubmit = (event) => {
event.preventDefault();
console.log("Submit");
this.props.handleClose();
};
static modules = {
toolbar: {
container: "#toolbar",
handlers: {
file: this.insertFile,
},
},
};
static formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"bullet",
"indent",
"link",
"image",
"color",
];
render() {
return (
<div className="popup-box">
<div className="box">
{/* <span className="close-icon" onClick={props.handleClose}>
x
</span> */}
<Form
onSubmit={this.handleSubmit}
style={{ paddingLeft: 30, paddingTop: 50 }}
>
<Row style={{ paddingBottom: 50 }}>
<Col sm={11} xs={11} md={11}>
<h1>Add new ticket </h1>
</Col>
<Col onClick={this.props.handleClose} m={1} xs={1} md={1}>
<h1 className="close-icon">X </h1>
</Col>
</Row>
<FormGroup>
<Row style={{ marginBottom: "25px" }}>
<Col sm={2}>
<h4>Description</h4>
</Col>
<Col sm={9}>
<div className="editor-wrapper">
<div className="editor-container">
<div className="text-editor">
<ReactQuill
value={this.state.editorHtml}
onChange={this.handleChange}
placeholder={this.props.placeholder}
modules={AddTicket.modules}
formats={AddTicket.formats}
/>
<Row>
<Col sm={6}>
<CustomToolbar />
</Col>
<Col sm={6}>
<FileUploader />
</Col>
</Row>
</div>
</div>
</div>
</Col>
</Row>
</FormGroup>
<Row>
<Col sm={2}></Col>
<Col>
<ActionButton text="Send" />
</Col>
</Row>
</Form>
</div>
</div>
);
}
}
export default AddTicket;
That's happening because you're not passing a function but rather calling it in onClick.
Fix:
<>
<button
style={{ border: 'none', background: 'white', paddingTop: -10 }}
onClick={() => handleClick()}
>
<span>
<Icon.Paperclip />
</span>
</button>
<input
type="file"
ref={hiddenFileInput}
style={{ display: 'none' }}
onChange={handleChange}
/>
</>
So i put together this Dynamic Form Below (Hotel Rooms) and inside it you can also create dynamic forms (Room Beds). It works, i can add rooms & Beds and the object array is returned in the console.log (onSubmit).
codesandbox: https://codesandbox.io/s/charming-hermann-nzpbu?file=/src/App.js
Issue:
If you add room1 and room2 and then delete room1, the array stays of length 2 and now it has "beds undefined" and it keeps growing when you add beds to other rooms! Help me solve this:
Object returned when you press next (in console). As you can see the array is not subtracting but it is also leaving the beds undefined when i delete a room:
listingDescription: "ss"
listingName: "aa"
pricePerMonth: 1
rooms: Array(2)
0: {roomname: "room2", beds: Array(1)}
1: {beds: undefined}
The Main Form:
import React from 'react';
import { useStateMachine } from 'little-state-machine';
import { useForm } from 'react-hook-form';
import { Row, Col, Input, InputNumber, Button, Form, Space } from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '#ant-design/icons';
import FormControl from 'components/UI/FormControl/FormControl';
import AddListingAction from './AddListingAction';
import { FormHeader, Title, FormContent, FormAction } from './AddListing.style';
import BedForm from "./BedForm";
const BasicInformation = ({ setStep }) => {
const { action, state } = useStateMachine(AddListingAction);
const { control, register, errors, setValue, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<Form onFinish={(e) => onSubmit(e)}>
<FormContent>
<FormHeader>
<Title>Step 1: Start with the basics</Title>
</FormHeader>
<Row gutter={30}>
<Col sm={12}>
<FormControl
label='Listing Name'
htmlFor='listingName'
error={errors.listingName && <span>This field is required!</span>}
>
<Form.Item
id='listingName'
name='listingName'
defaultValue={state.data.listingName}
control={control}
placeholder='Write a name for your listing here'
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
</FormControl>
</Col>
<Col sm={12}>
<FormControl
label='Price Per Night (USD)'
htmlFor='pricePerMonth'
>
<Form.Item
name='pricePerMonth'
id='pricePerMonth'
defaultValue={state.data.pricePerMonth}
control={control}
placeholder='00.00'
rules={[
{
required: true,
pattern: /^[0-9]*$/,
},
]}
>
<InputNumber min={0} />
</Form.Item>
</FormControl>
</Col>
</Row>
<FormControl
label='Listing Description'
htmlFor='listingDescription'
error={
errors.listingDescription && <span>This field is required!</span>
}
>
<Form.Item
id='listingDescription'
name='listingDescription'
defaultValue={state.data.listingDescription}
control={control}
placeholder='Tell people about your listing, rooms, location & amenities'
rules={[
{
required: true,
},
]}
>
<Input.TextArea rows={5} />
</Form.Item>
</FormControl>
<FormControl
label='How many rooms does your listing have?'
error={errors.guest && <span>This field is required!</span>}
>
{/* This is the Dynamic room Adder */}
<Form.List name='rooms'>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field) => (
<Space
key={field.key}
style={{ display: 'flex', marginBottom: 8 }}
align='start'
>
<Form.Item
{...field}
name={[field.name, 'roomname']}
fieldKey={[field.fieldKey, 'roomname']}
rules={[
{ required: true, message: 'Missing room name' },
]}
>
<Input placeholder='Room Name' />
</Form.Item>
{/* This is the Dynamic bed Adder */}
<Form.Item>
<BedForm fieldKey={field.key} />
</Form.Item>
<MinusCircleOutlined
onClick={() => {
remove(field.name);
console.log(field)
}}
/>
</Space>
))}
<Button
type='dashed'
onClick={() => {
add();
}}
block
>
<PlusOutlined /> Add room
</Button>
</div>
);
}}
</Form.List>
</FormControl>
</FormContent>
<FormAction>
<div className='inner-wrapper'>
<Button type='primary' htmlType='submit'>
Next
</Button>
</div>
</FormAction>
</Form>
);
};
export default BasicInformation;
The Child Form (BedForm)
import React from 'react';
import { Form, Input, Button, Space, Select } from 'antd';
import { PlusOutlined, MinusCircleOutlined } from '#ant-design/icons';
const { Option } = Select;
//#ATT:this was created to make nested dynamic elements! This is hard!
const BedForm = (props) => {
return (
<>
<Form.List name={[props.fieldKey, 'beds']}>
{(beds, { add, remove }) => {
return (
<div>
{beds.map((bed, index2) => (
<Space
key={bed.key}
style={{ display: 'flex', marginBottom: 8 }}
align='start'
>
<Form.Item
// name={"aar"}
{...bed}
name={[bed.name, 'bed']}
fieldKey={[bed.fieldKey, 'bed']}
key={index2}
// noStyle
rules={[
{
required: true, message: 'Beds Missing'
},
]}
>
<Select placeholder="Please select a Bed Type">
<Option value='double'>Double(2 person)</Option>
<Option value='single'>Single (1 person)</Option>
<Option value='king'>King Size (2 person)</Option>
<Option value='queen'>Queen Size (2 person)</Option>
<Option value='Bunk'>Bunk Bed (1 person)</Option>
<Option value='sofa'>Sofa Bed (1 person)</Option>
</Select>
</Form.Item>
{/* <MinusCircleOutlined
onClick={() => {
remove(bed.name);
}}
/> */}
</Space>
))}
<Form.Item>
<Button
type='dashed'
onClick={() => {
add();
}}
>
<PlusOutlined /> Add Bed
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</>
);
};
export default BedForm;
Pass field.name instead of field.key or fieldKey on App.js
https://codesandbox.io/s/trusting-wind-elsdz?file=/src/App.js