onClick call component with props - reactjs

Following is Navigation link with onclick event. I couldn't figure out how to call ItemModal component when navigation with onclick event is called. I have tried with following code, it's not working.
<strong onClick={() => <ItemModal modal={true} isAuthenticated={true} />}> Add Item </strong>
Below is ItemModal components with reactstrap modal to add new items.
ItemModal.js
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
} from "reactstrap";
import { connect } from "react-redux";
import { addItem } from "../actions/itemActions";
import PropTypes from "prop-types";
class ItemModal extends Component {
state = {
modal: false,
name: "",
};
static propTypes = {
isAuthenticated: PropTypes.bool,
};
toggle = () => {
this.setState({
modal: !this.state.modal,
});
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e) => {
e.preventDefault();
const newItem = {
name: this.state.name,
};
// Add item via addItem action
this.props.addItem(newItem);
//Close Modal
this.toggle();
};
render() {
return (
<div>
{this.props.isAuthenticated ? (
<Button
color="dark"
style={{ marginBottom: "2rem" }}
onClick={this.toggle}
>
Add Item
</Button>
) : (
<h4 className="mb-3 ml-4"> Please log in to manage items</h4>
)}
<Modal isOpen={this.state.modal} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}> Add to Shopping List</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="item">Item</Label>
<Input
type="text"
name="name"
id="item"
placeholder="Add shopping item"
onChange={this.onChange}
/>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Add Item
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
const mapStateToProps = (state) => ({
item: state.item,
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(mapStateToProps, { addItem })(ItemModal);
How I can call component on click so that reactstrap model (ItemModal) is opened?

Related

React. Passing Functions For A Modal

I am trying to Close a modal from a child component of the modal component, my problem is, I have to declare a function on the grand parent component for the gradchild component and my code to update it isn't working. My test app uses react-bootstrap and currently has no store lib. What I am trying todo is nest a "form" component within a "modal" component and pass the closing function to the form component within the modal component all on a grand parent. the code is as follows:
This is the main component which contains my Modal Component and Form Component
import { useEffect } from "react";
import { GetAllGrades } from "../../../Services/GradeApi";
import FormModal from "../../Layout/Modal/FormModal";
import AddGradeModal from "./AddGradeModal";
import EditGradeModal from "./EditGradeModal";
import GradeForm from "./GradeForm";
const AllGrades = () => {
const [grades, setGrades] = GetAllGrades();
useEffect(() => {
setGrades();
}, [grades, setGrades]);
return (
<table className="table .table-striped ">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">KYU</th>
<th scope="col">Required Session Time</th>
<th>
</th>
<th></th>
</tr>
</thead>
<tbody>
{grades?.map((grade) => (
<tr key={grade.id}>
<th>{grade.name}</th>
<td>{grade.kyu}</td>
<td>{grade.requiredLessionTime}</td>
<td>
</td>
<td>
<FormModal ModalTitle="Form Modal" TriggerButtonText="Test">
<GradeForm Grade={grade} ViewOnly={false}>
closeModal={????} </GradeForm> //not sure how not to set this property for the callback function here
</FormModal>
</td>
</tr>
))}
</tbody>
</table>
);
};
export default AllGrades;
Modal Component - I am trying to add its "handlClose" function to the child component so it can call and close the modal once the for is submitted.
import React, { useEffect, useState } from "react";
import { Button, Modal } from "react-bootstrap";
type FormModalProps = {
ModalTitle: string;
TriggerButtonText: string;
children?: React.ReactNode;
};
const FormModal = (props: FormModalProps) => {
const { ModalTitle, TriggerButtonText, children } = props;
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const childrenWithProps = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, {
closeModal: { handleClose },
});
}
return child;
}); // update the children with the correct function
return (
<>
<Button variant="primary" onClick={handleShow}>
{TriggerButtonText}
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>{ModalTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>{childrenWithProps}</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default FormModal;
this is the form - on the submit function I want to close the modal, so I pass the "handleClose" function from the parent.
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import { Grade, GradeFormProps } from "../../../Interface";
import { useEffect, useState } from "react";
import { Button, Form } from "react-bootstrap";
import { GradeValidationSchema } from "../../../Services/ValidationSchemas/GradeValidation";
const GradeForm = (props: GradeFormProps) => {
const [grade, setGrade] = useState<Grade>(props.Grade);
const { Grade, ViewOnly, closeModal } = props;
const {
handleSubmit,
control,
formState: { errors },
} = useForm<Grade>({
resolver: yupResolver(GradeValidationSchema),
defaultValues: grade,
});
const onSubmit = (data: Grade) => {
if (data.id === undefined) {
// AddMember({ Grade: data });
} else {
// UpdateMember({ Grade: data });
}
console.log("close Form");
closeModal && closeModal(null);
// closeModal.setShow(true);
};
useEffect(() => {
setGrade(Grade);
}, [setGrade, grade, Grade]);
return (
<Form onSubmit={handleSubmit(onSubmit)}>
<fieldset disabled={ViewOnly}>
<Form.Group className="mb-3" controlId="name">
<Form.Label>Licence Number</Form.Label>
<Controller
control={control}
name="name"
defaultValue=""
render={({ field: { onChange, onBlur, value, ref } }) => (
<Form.Control
onChange={onChange}
value={value}
ref={ref}
placeholder="Enter Name"
/>
)}
/>
{errors.name && (
<div className="text-danger">{errors.name?.message}</div>
)}
</Form.Group>
<Form.Group className="mb-3" controlId="kyu">
<Form.Label>KYU</Form.Label>
<Controller
control={control}
name="kyu"
render={({ field: { onChange, onBlur, value, ref } }) => (
<Form.Control
onChange={onChange}
value={value}
ref={ref}
placeholder="Enter Kyu"
/>
)}
/>
{errors.kyu && (
<div className="text-danger">{errors.kyu?.message}</div>
)}
</Form.Group>
<Form.Group className="mb-3" controlId="requiredLessionTime">
<Form.Label>Last Name</Form.Label>
<Controller
control={control}
name="requiredLessionTime"
render={({ field: { onChange, onBlur, value, ref } }) => (
<Form.Control
onChange={onChange}
value={value}
ref={ref}
placeholder="Enter required Session Time"
/>
)}
/>
{errors.requiredLessionTime && (
<div className="text-danger">
{errors.requiredLessionTime?.message}
</div>
)}
</Form.Group>
{!props.ViewOnly && (
<Button variant="primary" type="submit">
Submit
</Button>
)}
</fieldset>
</Form>
);
};
export default GradeForm;
Props for the Form
export interface GradeFormProps{
Grade: Grade,
ViewOnly: boolean,
closeModal: (params:any)=>any ;
}
any help would be appreciated.
I think, you just have a syntax issue in your FormModal component. handleClose shouldn't be wrapped in an object.
import React, { useEffect, useState } from "react";
import { Button, Modal } from "react-bootstrap";
type FormModalProps = {
ModalTitle: string;
TriggerButtonText: string;
children?: React.ReactNode;
};
const FormModal = (props: FormModalProps) => {
const { ModalTitle, TriggerButtonText, children } = props;
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const childrenWithProps = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, {
closeModal: handleClose
});
}
return child;
}); // update the children with the correct function
return (
<>
<Button variant="primary" onClick={handleShow}>
{TriggerButtonText}
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>{ModalTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>{childrenWithProps}</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default FormModal;
In the AllGrades component, you don't need to have closeModal in the JSX but this could be an old code fragment.
Additionally, I would recommend to rename the closeModal prop of GradeForm into something like onSuccess or onFinish, as the form doesn't know that it is rendered within a modal or not.

How would I go about setting up this modal to toggle?

I'm trying to get an editModal and a detailsModal to open on click. I set up actions and reducers for the modal so it can be stored in the global state and it works, but currently the modals are set to open automatically and I cannot close them. I believe it's something wrong with my logic, but it seems like it would be rather straightforward. Does anyone have any tips on how to complete this?
modalReducer.js
import { OPEN_MODAL } from "../actions/types";
const initialState = {
modal: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case OPEN_MODAL:
return {
...state,
modal: false,
};
default:
return state;
}
}
ClientEditModal.js
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
} from "reactstrap";
import { connect } from "react-redux";
import { editClient } from "../actions/clientActions";
import { openModal } from "../actions/modalActions";
import PropTypes from "prop-types";
class ClientEditModal extends Component {
componentDidMount() {
this.props.editClient();
this.props.openModal();
}
toggle = () => {
this.setState({
modal: !this.props.modal,
});
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e) => {
e.preventDefault();
// Close modal
this.toggle();
};
displayClient = (clients, _id) => {
return (
<FormGroup key={_id} timeout={500} classNames="fade">
<Label for="name"> Name </Label>
<Input
type="text"
name="name"
id="client"
value={clients.name}
onChange={this.onChange}
></Input>
<Label for="email"> Email </Label>
<Input
type="text"
name="email"
id="client"
value={clients.email}
onChange={this.onChange}
></Input>
<Label for="number"> Number </Label>
<Input
type="text"
name="number"
id="number"
value={clients.number}
onChange={this.onChange}
></Input>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Submit Client Edit
</Button>
</FormGroup>
);
};
render() {
const { clients } = this.props.client;
return (
// Split button into separate component
<div>
<Button
color="dark"
style={{ marginBottom: "2rem", marginLeft: "1rem" }}
onClick={this.toggle}
>
Edit
</Button>
<Modal
isOpen={this.props.modal}
toggle={this.toggle}
style={{ padding: "50px" }}
>
<ModalHeader toggle={this.toggle}> Edit</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
{clients.map(this.displayClient)}
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
ClientEditModal.propTypes = {
editClient: PropTypes.func.isRequired,
client: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
client: state.client,
modal: state.modal,
});
export default connect(mapStateToProps, { editClient, openModal })(
ClientEditModal
);
modalActions.js
import { OPEN_MODAL } from "./types";
export const openModal = () => {
return {
type: OPEN_MODAL,
};
};
The main issue is - your toggle function, !this.props.modal will invert the value of modal and the component always holds the value of true.
So, one option is to maintain another action for closing modal.
See the updated code snippets below.
Reducer
import { OPEN_MODAL } from "../actions/types";
const initialState = {
modal: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case OPEN_MODAL:
return {
...state,
modal: false,
};
case CLOSE_MODAL:
return {
...state,
modal: true,
};
default:
return state;
}
}
Actions
import { OPEN_MODAL } from "./types";
export const openModal = () => {
return {
type: OPEN_MODAL,
};
};
export const closeModal = () => {
return {
type: CLOSE_MODAL,
};
};
ClientModal.js
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
} from "reactstrap";
import { connect } from "react-redux";
import { editClient } from "../actions/clientActions";
import { openModal } from "../actions/modalActions";
import PropTypes from "prop-types";
class ClientEditModal extends Component {
componentDidMount() {
this.props.editClient();
this.props.openModal();
}
toggle = () => {
// this.setState({ //<---- setstate is not required as once the state in the store is updated, then the component is re-rendered automatically
// modal: !this.props.modal, //<---- once toggle is executed - it is always true
//});
this.props.closeModal()
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e) => {
e.preventDefault();
// Close modal
this.toggle();
};
displayClient = (clients, _id) => {
return (
<FormGroup key={_id} timeout={500} classNames="fade">
<Label for="name"> Name </Label>
<Input
type="text"
name="name"
id="client"
value={clients.name}
onChange={this.onChange}
></Input>
<Label for="email"> Email </Label>
<Input
type="text"
name="email"
id="client"
value={clients.email}
onChange={this.onChange}
></Input>
<Label for="number"> Number </Label>
<Input
type="text"
name="number"
id="number"
value={clients.number}
onChange={this.onChange}
></Input>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Submit Client Edit
</Button>
</FormGroup>
);
};
render() {
const { clients } = this.props.client;
return (
// Split button into separate component
<div>
<Button
color="dark"
style={{ marginBottom: "2rem", marginLeft: "1rem" }}
onClick={this.toggle}
>
Edit
</Button>
<Modal
isOpen={this.props.modal}
toggle={this.toggle}
style={{ padding: "50px" }}
>
<ModalHeader toggle={this.toggle}> Edit</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
{clients.map(this.displayClient)}
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
ClientEditModal.propTypes = {
editClient: PropTypes.func.isRequired,
client: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
client: state.client,
modal: state.modal,
});
export default connect(mapStateToProps, { editClient, openModal, closeModal })(
ClientEditModal
);

Can't get jest test to fire button onclick

I am completely new to both react and testing and am a bit stumped.
I was just wondering if someone could tell me why my test fails. I assume I making a basic mistake in how this should work.
I am trying to test a log in page. At the moment I am just trying to get my test to fire an onclick from a button and check that that a function has been called.
The log in component code can be seen below.
import React, { Component, Fragment } from "react";
import { Redirect } from "react-router-dom";
// Resources
//import logo from "assets/img/white-logo.png";
//import "./Login.css";
// Material UI
import {
withStyles,
MuiThemeProvider,
createMuiTheme
} from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Person from "#material-ui/icons/Person";
import InputAdornment from "#material-ui/core/InputAdornment";
// Custom Components
import Loading from "components/Loading/Loading.jsx";
// bootstrat 1.0
import { Alert, Row } from "react-bootstrap";
// MUI Icons
import LockOpen from "#material-ui/icons/LockOpen";
// remove
import axios from "axios";
// API
import api2 from "../../helpers/api2";
const styles = theme => ({
icon: {
color: "#fff"
},
cssUnderline: {
color: "#fff",
borderBottom: "#fff",
borderBottomColor: "#fff",
"&:after": {
borderBottomColor: "#fff",
borderBottom: "#fff"
},
"&:before": {
borderBottomColor: "#fff",
borderBottom: "#fff"
}
}
});
const theme = createMuiTheme({
palette: {
primary: { main: "#fff" }
}
});
class Login extends Component {
constructor(props, context) {
super(props, context);
this.state = {
username: "",
password: "",
isAuthenticated: false,
error: false,
toggle: true,
// loading
loading: false
};
}
openLoading = () => {
this.setState({ loading: true });
};
stopLoading = () => {
this.setState({ loading: false });
};
toggleMode = () => {
this.setState({ toggle: !this.state.toggle });
};
handleReset = e => {
const { username } = this.state;
this.openLoading();
api2
.post("auth/admin/forgotPassword", { email: username })
.then(resp => {
this.stopLoading();
console.log(resp);
})
.catch(error => {
this.stopLoading();
console.error(error);
});
};
handleSubmit = event => {
event.preventDefault();
localStorage.clear();
const cred = {
username: this.state.username,
password: this.state.password
};
api2
.post("auth/admin", cred)
.then(resp => {
console.log(resp);
localStorage.setItem("api_key", resp.data.api_key);
localStorage.setItem("username", cred.username);
return this.setState({ isAuthenticated: true });
})
.catch(error => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log("Error", error.message);
}
console.log(error.config);
return this.setState({ error: true });
});
};
handleInputChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
};
forgotPassword = () => {
console.log("object");
};
render() {
const { error } = this.state;
const { isAuthenticated } = this.state;
const { classes } = this.props;
if (isAuthenticated) {
return <Redirect to="/home/dashboard" />;
}
return (
<div className="login-page">
<video autoPlay muted loop id="myVideo">
<source
src=""
type="video/mp4"
/>
</video>
<div className="videoOver" />
<div className="midl">
<Row className="d-flex justify-content-center">
<img src={''} className="Login-logo" alt="logo" />
</Row>
<br />
<Row className="d-flex justify-content-center">
{error && (
<Alert style={{ color: "#fff" }}>
The username/password entered is incorrect. Try again!
</Alert>
)}
</Row>
<MuiThemeProvider theme={theme}>
<Row className="d-flex justify-content-center">
<TextField
id="input-username"
name="username"
type="text"
label="username"
value={this.state.username}
onChange={this.handleInputChange}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<Person className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
{this.state.toggle ? (
<Fragment>
<br />
<Row className="d-flex justify-content-center">
<TextField
id="input-password"
name="password"
type="password"
label="pasword"
value={this.state.password}
onChange={this.handleInputChange}
className={classes.cssUnderline}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<LockOpen className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
</Fragment>
) : (
""
)}
</MuiThemeProvider>
<br />
<Row className="d-flex justify-content-center">
{this.state.toggle ? (
<Button
className="button login-button"
data-testid='submit'
type="submit"
variant="contained"
color="primary"
onClick={this.handleSubmit}
name = "logIn"
>
Login
</Button>
) : (
<Button
className="button login-button"
type="submit"
variant="contained"
color="primary"
onClick={this.handleReset}
>
Reset
</Button>
)}
</Row>
<Row className="d-flex justify-content-center">
<p onClick={this.toggleMode} className="text-link">
{this.state.toggle ? "Forgot password?" : "Login"}
</p>
</Row>
</div>
<Loading open={this.state.loading} onClose={this.handleClose} />
</div>
);
}
}
export default withStyles(styles)(Login);
My current test which fails.
import React from 'react'
import {render, fireEvent, getByTestId} from 'react-testing-library'
import Login from './login'
describe('<MyComponent />', () => {
it('Function should be called once', () => {
const functionCalled = jest.fn()
const {getByTestId} = render(<Login handleSubmit={functionCalled} />)
const button = getByTestId('submit');
fireEvent.click(button);
expect(functionCalled).toHaveBeenCalledTimes(1)
});
});
I'm also fairly new to React Testing Library, but it looks like your button is using this.handleSubmit, so passing your mock function as a prop won't do anything. If you were to pass handleSubmit in from some container, then I believe your tests, as you currently have them, would pass.

Error message "shap is not a function" - what shall I do?

I get an error message I didn't figure out how to get rid off:
yup__WEBPACK_IMPORTED_MODULE_12__.object(...).shap is not a function
import React from 'react'
import Button from '#material-ui/core/Button'
import Dialog from '#material-ui/core/Dialog'
import DialogActions from '#material-ui/core/DialogActions'
import DialogContent from '#material-ui/core/DialogContent'
import DialogContentText from '#material-ui/core/DialogContentText'
import DialogTitle from '#material-ui/core/DialogTitle'
import TextField from '#material-ui/core/TextField'
import { Link } from 'gatsby'
import DisplayOutput from '../pages/DisplayOutput'
import { Formik, Field, Form, ErrorMessage } from 'formik'
import * as Yup from 'yup'
class DialogFormWithFormik extends React.Component {
constructor(props) {
super(props)
this.state = {
open: false,
dialogModal: 'none',
}
}
handleClickOpen = () => {
this.setState({ open: true })
this.setState({ dialogModal: 'login' })
}
handleRegisterClickOpen = () => {
this.setState({ open: true })
this.setState({ dialogModal: 'register' })
}
handleClose = () => {
this.setState({ dialogModal: false })
}
onSubmit = values => {
console.log(values)
alert('values submitted')
}
form = props => {
return (
<div>
<Button
variant="outlined"
color="primary"
onClick={() => this.handleClickOpen()}
>
Login
</Button>
<Button
variant="outlined"
color="primary"
onClick={() => this.handleRegisterClickOpen()}
>
Register
</Button>
<Dialog
onClose={() => this.handleClose()}
aria-labelledby="customized-dialog-title"
open={this.state.dialogModal === 'login'}
>
<DialogTitle id="form-dialog-title">
To Display Student Data
</DialogTitle>
<DialogContent />
<form onSubmit={props.handleSubmit}>
<TextField
label="Username"
type="text"
margin="normal"
name="userName"
/>
<ErrorMessage name="userName" />
<br />
<TextField
label="Password"
type="password"
autoComplete="current-password"
margin="normal"
/>
<ErrorMessage name="password" />
<DialogActions>
<nav>
<Button color="primary">Login</Button>
</nav>
<br />
<Button onClick={() => this.handleClose()}>Cancel</Button>
</DialogActions>
</form>
</Dialog>
</div>
)
}
schema = () => {
const schema = Yup.object().shap({
userName: Yup.string().required(),
password: Yup.string().required(),
})
return schema
}
render() {
return (
<div align="center">
<Formik
initialValues={{
userName: '',
password: '',
}}
onSubmit={this.onSubmit}
render={this.form}
validationSchema={this.schema()}
/>
</div>
)
}
}
export default DialogFormWithFormik

How to submit form component in modal dialogue using antd react component library

In my component's render method I have antd Modal component as a parent and antd Form component as a child:
render() {
const myForm = Form.create()(AddNewItemForm);
...
return (
...
<Modal
title="Create new item"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
wrapClassName="vertical-center-modal"
okText="Save new item"
width="600"
>
<myForm />
</Modal>
...
How can I submit my form by clicking the Modals Save button?
There is a new solution that looks much cleaner:
<Form id="myForm">
...
<Modal
...
footer={[
<Button form="myForm" key="submit" htmlType="submit">
Submit
</Button>
]}
>
<CustomForm />
</Modal>
This works because of the Button's form attribute. Browser support
Original solution's author: https://github.com/ant-design/ant-design/issues/9380
My solution is using hooks
import { Button, Modal, Form } from 'antd';
export default function ModalWithFormExample() {
const [visible, setVisible] = useState(false);
const [form] = Form.useForm();
const showModal = () => {
setVisible(true)
}
const handleSubmit = (values) => {
console.log(values)
}
const handleCancel = () => {
setVisible(false)
form.resetFields()
};
return (
<>
<Button onClick={showModal}>Open Modal</Button>
<Modal visible={visible} onOk={form.submit} onCancel={handleCancel}>
<Form form={form} onFinish={handleSubmit}>
{/* Any input */}
</Form>
</Modal>
</>
)
}
You can study official example: https://ant.design/components/form/#components-form-demo-form-in-modal
My solution was to wrap modal dialogue and form components in a new wrapper parent component in which I validate the child form component in handleCreate method. I have used the ref attribute to reference the myForm child component inside the FormOnModalWrapper component. I am passing the parent handlers via props from the wrapper parent component to myForm component instance.
class FormOnModalWrapper extends React.Component {
...
constructor(props) {
this.state =
{
visible: false
....
}
...
showModal = () => {
this.setState({
visible: true,
});
}
handleCreate = () => {
const form = this.form;
form.validateFields((err, values) => {
if (err) {
return;
}
console.log('Received values of form: ', values);
form.resetFields();
this.setState({ visible: false });
});
}
saveFormRef = (form) => {
this.form = form;
}
render() {
...
const myForm= Form.create()(CrateNewItemFormOnModal);
...
return (
<div>
<Button onClick={this.showModal}>Add</Button>
<myForm
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
ref={this.saveFormRef}
/>
</div>
);
}
In CrateNewItemFormOnModal component class I have a modal dialogue component as a parent and form component as a child:
export default class AddNewItemForm extends React.Component {
render() {
...
const { visible, onCancel, onCreate, form } = this.props;
...
return (
<Modal
title="Create new item"
visible={visible}
onOk={onCreate}
onCancel={onCancel}
okText="Create"
>
<Form>
...
</Form>
</Modal>
);
}
My solution was to disable the modal's footer and create my own submit button:
<Modal footer={null}>
<Form onSubmit={this.customSubmit}>
...
<FormItem>
<Button type="primary" htmlType="submit">Submit</Button>
</FormItem>
</Form>
</Modal>
No need to wrap the modal with this solution.
Now, react hooks are out you can achieve the same thing using hooks also. By creating a wrapper component for the modal and used that component where the form is.
Wrapper Component:
<Modal
visible={state}
centered={true}
onCancel={() => setState(false)}
title={title}
destroyOnClose={true}
footer={footer}>
{children}
</Modal>
Form Component:
<WrapperModal
state={modalState}
setState={setModal}
title='Example Form'
footer={[
<button onClick={handleSubmit}>
SUBMIT
<button/>
]}>
<Form>
<Form.Item label='name '>
{getFieldDecorator('name ', {
rules: [
{
required: true,
message: 'please enter proper name'
}
]
})(<Input placeholder='name'/>)}
</Form.Item>
</Form>
</WrapperModal>
here i had created a wrapper modal component which have all the necessary api for the modal, also i am creating a custom buttons for my modal
My solution 1st solution
...
handleOk = (e) => {
e.preventDefault();
this.form.validateFields((err, values) => {
//do your submit process here
});
}
//set ref form
formRef = (form) => {
this.form = form;
}
render() {
const myForm = Form.create()(AddNewItemForm);
...
return (
...
<Modal
title="Create new item"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
wrapClassName="vertical-center-modal"
okText="Save new item"
width="600"
>
<myForm
ref={this.formRef}
/>
</Modal>
...
or you can use this solution
...
handleOk = (e) => {
e.preventDefault();
this.form.validateFields((err, values) => {
//do your submit process here
});
}
render() {
const myForm = Form.create()(AddNewItemForm);
...
return (
...
<Modal
title="Create new item"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
wrapClassName="vertical-center-modal"
okText="Save new item"
width="600"
>
<myForm
wrappedComponentRef={(form) => this.formRef = form}
/>
</Modal>
...
The idea is to set the ref for wrapped the form component.
Please see the reference below.
Reference
Simple way to do this in 2021 is making customized footer for modal
import { useState } from 'react'
import { Modal, Button, Form, Input } from 'antd'
export default function BasicModal() {
const [form] = Form.useForm()
const [isModalVisible, setIsModalVisible] = useState(false)
const showModal = () => setIsModalVisible(true)
const handleCancel = () => {
setIsModalVisible(false)
form.resetFields()
}
const handleOk = () => {
form.submit()
}
const onFinish = () => {
console.log('Form submited!')
setIsModalVisible(false)
}
return (
<>
<Button type="primary" onClick={showModal}>
Show Modal
</Button>
<Modal
title="Basic Modal"
visible={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={handleOk}>
Submit
</Button>,
]}
>
<Form labelCol={{ xs: { span: 6 } }} wrapperCol={{ xs: { span: 12 } }} form={form} onFinish={onFinish} scrollToFirstError>
<Form.Item name="input1" label="Input 1" rules={[{ required: true, message: "This field is required." }]}>
<Input />
</Form.Item>
<Form.Item name="input2" label="Input 2" rules={[{ required: true, message: "This field is required." }]}>
<Input />
</Form.Item>
</Form>
</Modal>
</>
)
}

Resources