Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using multiple modal forms in one component

Tags:

reactjs

antd

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

like image 452
srdjanRakic Avatar asked Sep 16 '25 01:09

srdjanRakic


1 Answers

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 Buttons and CollectionCreateForms:

<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 refs, writing your form as a controlled component

like image 186
Luke Willis Avatar answered Sep 19 '25 15:09

Luke Willis