This is the 17th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

How do I write popovers quickly

What would you say if you were asked to click the New button, pop up a form and submit it?

It’s easy, 60 or 70 lines of code.

import { Button, Form, Input, Modal, Select } from 'antd';
import './style.less';
import { useState } from 'react';

export default function TestPage() {
  const [visiable, setVisiable] = useState(false);
  const [form] = Form.useForm();
  // Open the popover
  const open = () = > {
    setVisiable(true);
  };
  // Close the popover
  const close = () = > {
    setVisiable(false);
  };
  // Click OK to submit form
  const submit = () = >{
    form.submit()
  }
  // Get the form data after submission, request the interface, reset the form and close
  const onSubmit = (values) = >{
    console.log(values)
    //await fetch ...
    form.resetFields();
    close()
  }
  return (
    <div>
      <div className="text-center">
        <Button type="primary" onClick={open}>new</Button>
      </div>
      <Modal 
          wrapClassName="modal-wrap"
          okText="Submit"
          cancelButtonProps={{ shape: 'round'}}okButtonProps={{ shape: 'round'}}width={600}
          visible={visiable}
          title="New User" 
          onCancel={close} 
          onOk={submit}
      >
        <div className="form">
          <Form form={form} labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} onFinish={onSubmit}>
            <Form.Item
              label="Username"
              name="username"
              rules={[{ required: true.message: 'Please input  username!' }]}
            >
              <Input />
            </Form.Item>
            <Form.Item
              label="User email"
              name="mail"
              rules={[{ required: true.message: 'Please input mail!' }]}
            >
              <Input />
            </Form.Item>
            <Form.Item
              label="Department"
              name="depart"
              rules={[{ required: true.message: 'Please input depart!' }]}
            >
              <Select>
                <Select.Option value={1}>The Marketing Department</Select.Option>
                <Select.Option value={2}>Finance dept.</Select.Option>
                <Select.Option value={3}>Research and development department</Select.Option>
              </Select>
            </Form.Item>
          </Form>
        </div>
      </Modal>
    </div>
  );
}
Copy the code

Any questions? No problem.

But now there’s a requirement to open the new user popover in 10 places. How to do? Copy bai!

Is there a problem with that? No problem.

Here comes another requirement: you also need a new department popover. How to do? Continue copy bai!

Change the title of the popover, change the fields and submit of the form.

Does so much copy matter? It doesn’t matter. Because it’s quick.

But now comes a requirement: add a field to the new user form

So here’s the problem! How do I know which files MY copy code is scattered in? Will it be corrected?

At this time think of, we have a killer mace, called reuse

So the question is, how do you reuse it?

Component multiplexing Trilogy

Component reuse looks simple, but many people are either easy to use or painful to use; Or painful to do and complicated to use.

So if you reuse this new user popover, the first question is what are you going to reuse?

Clarify objects and decide what to reuse

We have three objects: new user Page Page, Button popup, Modal popup, form From, these three objects have some functions respectively:

  • Page: provides popover open/close function (open/close), provides method to trigger form submission (submit), provides form object (form.useform ()), receives form output and submits to server
  • Button: Open popover (call open)
  • Modal: Provides click cancel and confirm events (onCancel/onOk)
  • Form: Form validation, which provides an onFinish event to output Form values

The most complex and variable thing here is the popover and its contents, so the popover and its contents (forms) need to be reused the most.

So what’s holding us back?

The Page needs a Form object to submit the form, and you need to turn Off Modal after the form submission. Who do these actions belong to?

To determine the boundary

The simplest object here is Button, which is a fool and only has an onClick action that depends on the page.

Modal is also not complicated, it provides cancellation and confirmation callbacks, and the content is also provided externally. So when we reuse a Modal, we use its style configuration

Form: We reuse a Form and naturally want to reuse the submission logic of the Form. Therefore, in addition to form validation, it should also assume the responsibility of the request server.

Into components

Encapsulate useModal, remove Modal:

useModal.tsx:

import { Modal } from 'antd';
import type { ModalProps } from 'antd';
import * as React from 'react';
import type { MutableRefObject } from 'react';
interface PropsType<T> extends Omit<ModalProps, 'onOk'> {
  onOk: (ref: MutableRefObject<T | undefined>) = > void;
}
function withModal<T = any> (modalProps? : ModalProps, slotProps? :any) {
  return function (Slot: React.FC<any>) {
    return (props? : PropsType
       ) = > {
      const ref = React.useRef<T>();
      return (
        <div>
          <Modal
            wrapClassName="modal-wrap"
            okText="Submit"
            cancelButtonProps={{ shape: 'round'}}okButtonProps={{ shape: 'round'}}width={600}
            {. modalProps}
            {. props}
            onOk={()= >props? .onOk? .(ref)} ><Slot {. slotProps} ref={ref} close={props? .onCancel} />
          </Modal>
        </div>
      );
    };
  };
}
export default withModal;

Copy the code

Detach the form and expose the REF

UserForm.tsx:

import { Form, Input, Select } from 'antd';
import type { FormInstance } from 'antd/es/form';
import React from 'react';
typePropsType = React.PropsWithChildren<{ afterSubmit? :(values: any, form: FormInstance<any>) = > void; } >.const UserForm = (props: PropsType, ref? : React.ForwardedRef
       ) = > {
  const [form] = Form.useForm();
  // Get the form data after submission, request the interface, reset the form and close
  const onSubmit = (values: any) = > {
    console.log(values);
    //await fetch ...form.resetFields(); props.afterSubmit? .(values, form); };return (
    <div className="form">
      <Form
        onFinish={onSubmit}
        ref={ref}
        form={form}
        labelCol={{ span: 4 }}
        wrapperCol={{ span: 16 }}
      >
        <Form.Item
          label="Username"
          name="username"
          rules={[{ required: true.message: 'Please input  username!' }]}
        >
          <Input />
        </Form.Item>
        <Form.Item
          label="User email"
          name="mail"
          rules={[{ required: true.message: 'Please input mail!' }]}
        >
          <Input />
        </Form.Item>
        <Form.Item
          label="Department"
          name="depart"
          rules={[{ required: true.message: 'Please input depart!' }]}
        >
          <Select>
            <Select.Option value={1}>The Marketing Department</Select.Option>
            <Select.Option value={2}>Finance dept.</Select.Option>
            <Select.Option value={3}>Research and development department</Select.Option>
          </Select>
        </Form.Item>
      </Form>
    </div>
  );
};
export default UserForm;

Copy the code

forwardRef

For this particular scenario, the submission of the form is controlled by useModal, so you need to expose the form or ref; It is also possible to provide a Submit button by the form itself and hide the modal footer, but this is not cost-effective when there are many forms.

Using useModal:

Const UserFormModal = withModal({popover props}, {popover content component (form) props})(react.forwardref (UserForm));Copy the code

In the end, the page has half the code missing

export default function TestPage() { const [visiable, setVisiable] = useState(false); // Unavailable const open = () => {setVisiable(true); }; // Unavailable const close = () => {setVisiable(false); }; Const submit = (ref: MutableRefObject<FormInstance>) => {ref.current.submit(); }; const afterSubmit = () => { close(); }; Const UserFormModal = withModal({title: 'New user'}, {afterSubmit})(reace.forwardref (UserForm)); Return (<div> <div className="text-center"> <Button type="primary" onClick={open}> New </Button> </div> <UserFormModal visible={visiable} onCancel={close} onOk={submit} /> </div> ); }Copy the code

Embrace the hooks

This is typical HOC thinking, but if we use hooks, this code could be reduced by 50%. Next, how to package useModal