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

In this article, we introduced the simple encapsulation idea of useModal, but this method has a side effect: popover closing motion effect is no longer. If that’s acceptable, look at this:

Added a confirmLoading effect that makes popovers close and open again.

The code is as follows: testPage.tsx

export default function TestPage() {
  const {open,FormModal: UserModal} = useFormModal({title:'New User'},React.forwardRef(UserForm))

  return (
    <div>
      <div className="text-center">
        <Button type="primary" onClick={open}>new</Button>
      </div>
      <UserModal depart={1}   />
    </div>
  );
}

Copy the code

useFormModal.tsx

const useFormModal = function <T> (modalProps: ModalProps, Slot: React.ComponentType<T>) {
  const [visiable, setVisiable] = useState(false);
  const [loading, setLoading] = useState(false);
  const open = () = > {
    setVisiable(true);
  };
  const close = () = > {
    setVisiable(false);
  };
  const FormModal = (slotProps: T) = > {
    const onCancel = () = > {
      close();
    };
    const ref = React.useRef<FormInstance>();
    const ok = () = >{ ref.current? .submit(); };return (
      <Modal
        onCancel={onCancel}
        onOk={ok}
        visible={visiable}
        wrapClassName="modal-wrap"
        okText="Submit"
        cancelButtonProps={{ shape: 'round'}}okButtonProps={{ shape: 'round'}}confirmLoading={loading}
        width={600}
        {. modalProps}
      >
        <Slot
          ref={ref}
          {. slotProps}
          afterSubmit={()= > {
            setLoading(false);
            close();
          }}
          beforeSubmit={() => setLoading(true)}
        />
      </Modal>
    );
  };

  return {
    FormModal,
    open,
  };
};
export default useFormModal;
Copy the code

UserForm.tsx

interfaceUserFormPropsType { depart? :number; beforeSubmit? :(values: any) = > void; afterSubmit? :(values: any, form: FormInstance<any>) = > void;
}
const UserForm = (props: React.PropsWithChildren
       
        , ref? : React.ForwardedRef
        
       ) = > {
  const [form] = Form.useForm();

  const onSubmit = async (values: any) => { props.beforeSubmit? .(values);// simulate the request
    await new Promise((r) = > {
      setTimeout(r, 3000); }); props.afterSubmit? .(values, form); message.success('Operation completed ~')
    form.resetFields();
  };
  return (
    <div className="form">
      <Form
        onFinish={onSubmit}
        ref={ref}
        form={form}
        labelCol={{ span: 4 }}
        wrapperCol={{ span: 16 }}
      >
        <Form.Item
          label="Username"
          name="uname"
          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! '}}]initialValue={props.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>
  );
};
Copy the code

The reason for both bugs is the same

Causes and Solutions

The code in question is here:

.const useFormModal = function(){
    const [visiable, setVisiable] = useState(false);
    const [loading, setLoading] = useState(false); .const FormModal = (slotProps: T) = > {
        return  <Modal  
                    visible={visiable}
                    confirmLoading={loading}
                >.</Modal>}}Copy the code

“Unavailable” or “setVisiable” calls result in const FormModal=… The rendered popover will no longer be the same popover.

So as long as you can keep an instance of the FormModal function, you can solve this problem. But if we use useCallback or useMemo:

 const FormModal = () =>{}
return {
    FormModal: useCallback(FormModal,[]),
    open,
  };
Copy the code

Writing this will result in the component not updating at all, so we will also need to change

    const [visiable, setVisiable] = useState(false);
    const [loading, setLoading] = useState(false);
Copy the code

Put it in FormModal. How do you open up the POPover API? With the ref

UseRef, forwardRef, and useImperativeHandle

  • The first thing you do is create a ref to FormModal with useRef

Notice this is the bestDon’t use createRefLater articles will explain the different functions of several REF apis

  • The ref also needs to be open to the outside world, and use useImperativeHandle to add open and close methods to it for external invocation

  • External invocation:

The argument to open can be used as the initial props for the form component and is optional.

The code is relatively simple, but the type definition is somewhat complex, as shown in the code reference:

Reference code

useFormModal.tsx


type ModalRefType<T> = { open: (initProp? : Partial
       ) = > void; close: () = > void } | undefined;

const useFormModal = function <T> (modalProps: Partial<ModalProps>, Slot: React.ComponentType<T>) {
  const modalRef = useRef<ModalRefType<T>>();

  const FormModal = forwardRef<ModalRefType<T>, T>((slotProps, mRef) = > {
    const [visiable, setVisiable] = useState(false);
    const [loading, setLoading] = useState(false);
    const [slotInitProp, setSlotInitProp] = useState<Partial<T>>();
    const open = (initProp? : Partial
       ) = > {
      if (initProp) {
        setSlotInitProp(initProp);
      }
      setVisiable(true);
    };
    const close = () = > {
      setVisiable(false);
    };
    useImperativeHandle(mRef, () = > ({ open, close }));
    const onCancel = () = > {
      close();
    };
    const formRef = React.useRef<FormInstance>();
    const ok = () = >{ formRef.current? .submit(); };return (
      <Modal
        onCancel={onCancel}
        onOk={ok}
        visible={visiable}
        wrapClassName="modal-wrap"
        okText="Submit"
        cancelButtonProps={{ shape: 'round'}}okButtonProps={{ shape: 'round'}}confirmLoading={loading}
        width={600}
        {. modalProps}
      >
        <Slot
          ref={formRef}
          {. slotProps}
          {. slotInitProp}
          afterSubmit={()= > {
            setLoading(false);
            close();
          }}
          beforeSubmit={() => setLoading(true)}
        />
      </Modal>
    );
  });
  return {
    FormModal: useCallback((props: PropsWithoutRef<T>) = > {
      return <FormModal ref={modalRef} {. props} / >;
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []),
    modalRef,
  };
};
Copy the code

TestPage.tsx

export default function TestPage() {
  const { modalRef, FormModal: UserModal } = useFormModal(
    { title: 'New User' },
    React.forwardRef(UserForm),
  );

  return (
    <div>
      <div className="text-center">
        <Button type="primary" onClick={()= >modalRef.current? The open ({1} depart:)} > new</Button>
      </div>
      <UserModal  />
    </div>
  );
}
Copy the code