[React]基于Antd的FormModal的组件封装以及useFormModal的hooks封装

React基于Antd的FormModal的组件封装以及useFormModal的hooks封装

场景

很常见,打开弹窗输入表单等...

封装后,弹窗自行挂载到body上,只需关注表达逻辑和打开关闭逻辑,其它的已经帮你管理好了

源码

ts 复制代码
import React, { useRef, useMemo, memo, forwardRef, useCallback, useState, useImperativeHandle, useEffect } from 'react';
import { Modal, Form } from 'antd';
import type { ModalProps } from 'antd';
import { createPortal, render, unmountComponentAtNode } from 'react-dom';

export const MyModal = memo(forwardRef((props: any, ref) => {
  useEffect(() => {
    console.log('modal had mounted')
  }, [])
  const [form] = Form.useForm();
  const [modalChildren, setModalChildren] = useState<React.ReactElement | null>(null);
  const [modalProps, setModalProps] = useState<ModalProps>({
    visible: false,
    ...(props ?? {})
  });
  const typeRef = useRef<string>();

  const onFinish = useCallback((values: any) => {
    modalProps.onOk?.(values);
  }, [form, modalProps]);

  const onClose = useCallback(() => {
    if (typeRef.current === 'form') {
      form.resetFields();
    }
    setModalProps((source) => ({
      ...source,
      visible: false,
    }));
  }, [form]);

  const onOpen = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: true,
    }));
  }, [form]);

  useImperativeHandle(ref, () => ({
    injectChildren: (element) => {
      setModalChildren(element);
    },
    injectModalProps: (props) => {
      console.log(props)
      setModalProps((source) => {
        return {
          ...source,
          ...props,
        }
      });
    },
    open: () => {
      onOpen();
    },
    close: () => {
      onClose();
    },
    setFieldsValue: (values: any) => {
      form.setFieldsValue?.(values);
    },
    setType: (type: string) => {
      typeRef.current = type;
    }
  }), []);

  const handleOk = useCallback((e: any) => {
    if (typeRef.current === 'form') {
      form.submit();
    } else {
      modalProps.onOk?.(e);
    }
  }, [form, modalProps]);

  return (
    <Modal
      {...modalProps}
      onCancel={onClose}
      onOk={handleOk}
    >
      {
        modalChildren
          ? React.cloneElement(modalChildren, typeRef.current === 'form'
            ? {
                onFinish,
                form,
                onClose,
              }
            : { onClose })
          : null
      }
    </Modal>
  )
}));

interface modalRefType {
  open: () => void;
  close: () => void;
  injectChildren: (child: React.ReactElement) => void;
  injectModalProps: (props: ModalProps) => void;
  setFieldsValue: (values: any) => void;
  setType: (type: string) => void;
}

interface openArgType extends ModalProps {
  children?: React.ReactElement,
  type?: 'form' | 'default',
  initialValues?: {
    [key: string]: any;
  },
}

const useMyModal = () => {
  const modalRef = useRef<modalRefType>();
  const handle = useMemo(() => {
    return {
      open: ({ children, type, initialValues, ...rest }: openArgType) => {
        console.log('modalRef.current: ', modalRef.current);
        modalRef.current?.setType(type ?? '');
        modalRef.current?.injectChildren(children ?? <div>111</div>);
        modalRef.current?.injectModalProps(rest);
        modalRef.current?.open();
        if (initialValues && type === 'form') {
          modalRef.current?.setFieldsValue?.(initialValues);
        }
      },
      close: () => {
        modalRef.current?.close();
      }
    };
  }, []);

  const containerRef = useRef<any>(document.createDocumentFragment())
  useEffect(() => {
    render(createPortal(<MyModal key="my-modal" ref={modalRef} />, document.body) as any, containerRef.current)

    return () => {
      unmountComponentAtNode(containerRef.current)
    }
  }, [])

  return [handle] as const;
}

export default useMyModal

使用Demo

ts 复制代码
const [modalHandler] = useMyModal()

// 表单组件内容
const AvailabilityForm = (props) => {
  return (
    <Form
      name="availability_form"
      {
      ...props
      }
    >
      <Form.Item
        name="time"
        rules={REQUIRED_RULE}
      >
        <DatePicker.RangePicker className='w-full' />
      </Form.Item>
    </Form>
  )
}

// 点击发布  
const handlePublish = useMemoizedFn(async () => {
    modalHandler.open({
      title: 'Available Date',
      type: 'form',
      initialValues: {},
      children: <AvailabilityForm></AvailabilityForm>,
      onOk: async (values: any) => {
        console.log('form vals', values);
        const timeRes: any[] = []

        console.log(Object.entries(values))
        Object.entries(values).forEach((item: any) => {
          const timeVal: any[] = item[1]
          if (Array.isArray(timeVal) && timeVal.length === 2) {
            timeRes.push({
              startTime: dayjs(timeVal[0]).utc().valueOf(),
              endTime: dayjs(timeVal[1]).utc().valueOf(),
            })
          }
        })

        try {
          const res = await netPublishListing(listingId, {
            availability: timeRes
          })

          if (res) {
            message.success('Success to publish')
            fetchItem()
            modalHandler.close();
          }
          console.log(res)
        } catch (err) {
          console.log('err: ', err);
        }
      },
      destroyOnClose: true,
      maskClosable: false
    });
  })
相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁5 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄5 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95275 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大5 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师5 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端