I have a page with many buttons, and would like each button to open its own modal form when clicked. I have a problem where using the same form/modal multiple times doesn't result in unique forms. For example, clicking the first button and then typing "asdf" into the form will also populate the form controlled by the second button.
How can I reuse my modals/forms and still keep their data distinct?
My code:
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Button, Modal, Form, Input, Radio } from 'antd';
const FormItem = Form.Item;
const CollectionCreateForm = Form.create()(
(props) => {
const { visible, onCancel, onCreate, form } = props;
const { getFieldDecorator } = form;
return (
<Modal
visible={visible}
title="Create a new collection"
okText="Create"
onCancel={onCancel}
onOk={onCreate}
>
<Form layout="vertical">
<FormItem label="Title">
{getFieldDecorator('title', {
rules: [{ required: true, message: 'Please input the title of collection!' }],
})(
<Input />
)}
</FormItem>
<FormItem label="Description">
{getFieldDecorator('description')(<Input type="textarea" />)}
</FormItem>
<FormItem className="collection-create-form_last-form-item">
{getFieldDecorator('modifier', {
initialValue: 'public',
})(
<Radio.Group>
<Radio value="public">Public</Radio>
<Radio value="private">Private</Radio>
</Radio.Group>
)}
</FormItem>
</Form>
</Modal>
);
}
);
class CollectionsPage extends React.Component {
state = {
visible: false,
};
showModal = () => {
this.setState({ visible: true });
}
handleCancel = () => {
this.setState({ visible: false });
}
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() {
return (
<div>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
</div>
);
}
}
ReactDOM.render(<CollectionsPage />, document.getElementById('container'));
interactive sandbox
The problem is that each form is given the same props, so they are treated as the same form. In your render
function, you create multiple Button
s and CollectionCreateForm
s:
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
// repeated...
The problem with that is the fact you're passing the same props into each of these. You're only setting up a single ref
and a single state
. Each form is the exact same form. When one modal is visible, they're all visible.
Instead of this, you need to keep separate state and refs for each form. (Please keep reading and don't just copy this code... this is bad code.)
class CollectionsPage extends React.Component {
state = {
visible1: false,
visible2: false
};
showModal1 = () => {
this.setState({ visible1: true });
}
showModal2 = () => {
this.setState({ visible2: true });
}
handleCancel1 = () => {
this.setState({ visible1: false });
}
handleCancel2 = () => {
this.setState({ visible2: false });
}
handleCreate1 = () => {
// ...
this.setState({ visible1: false });
}
handleCreate2 = () => {
// ...
this.setState({ visible2: false });
}
saveFormRef1 = (form) => {
this.form1 = form;
}
saveFormRef2 = (form) => {
this.form2 = form;
}
render() {
return (
<div>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef1}
visible={this.state.visible1}
onCancel={this.handleCancel1}
onCreate={this.handleCreate1}
/>
<Button type="primary" onClick={this.showModal}>New Collection</Button>
<CollectionCreateForm
ref={this.saveFormRef2}
visible={this.state.visible2}
onCancel={this.handleCancel2}
onCreate={this.handleCreate2}
/>
</div>
);
}
}
I show you that to demonstrate how to fix your problem, but this is a poor way of implementing it. As you add more forms & modals, this will just spiral out of control. We can improve by delegating the logic to child components.
First, we can create a generic ModalToggle
that encapsulates the button & form/modal in one:
const CollectionsPage = () => (
<div>
<ModalToggle
label="New Collection"
modal={ CollectionFormModal }
/>
<ModalToggle
label="New Collection"
modal={ CollectionFormModal }
/>
</div>
);
We can reuse the ModalToggle
at will by moving all of the state inside so it is self sufficient:
class ModalToggle extends React.Component {
state = {
visible: false
};
toggleModal = () => {
this.setState(prevState => ({ visible: !prevState.visible }));
}
render() {
const Modal = this.props.modal;
return (
<div>
<Button type="primary" onClick={this.toggleModal}>{this.props.label}</Button>
<Modal
visible={this.state.visible}
toggleVisibility={this.toggleModal}
/>
</div>
);
}
}
Then, we just need to add a CollectionFormModal
to take care of the other logic that was previously in CollectionsPage
:
class CollectionFormModal extends React.Component {
handleCancel = () => {
this.props.toggleVisibility();
}
handleCreate = () => {
// ...
this.props.toggleVisibility();
}
render() {
return (
<CollectionCreateForm
onCancel={this.handleCancel}
onCreate={this.handleCreate}
visible={this.props.visible}
/>
);
}
}
You can further improve on this by moving the Modal
parts of CollectionCreateForm
up into CollectionFormModal
, and instead of using ref
s, writing your form as a controlled component
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With