
Refine v5 Mantine useModalForm 实战指南在弹窗中完成记录的创建、编辑与克隆【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本篇技术指南聚焦于 Refine v5 中 Mantine UI 集成的useModalFormHook讲解如何在Modal弹窗内完成数据记录的创建create、编辑edit与克隆clone三种核心操作。你将掌握useModalForm的完整用法、全部配置项与返回值、syncWithLocation的 URL 同步机制以及如何基于examples/form-mantine-use-modal-form示例搭建一个可复用的弹窗表单页面。useModalForm 是什么从 useForm 扩展而来的弹窗表单 HookuseModalForm是refinedev/mantine包提供的 Hook它允许你在一个弹窗Modal组件内部管理表单。它提供了一组专门处理表单 弹窗交互的方法例如打开弹窗、关闭弹窗、提交表单、自动复位表单等。从源码结构看useModalForm是直接从useForm扩展而来的。在 packages/mantine/src/hooks/form/useModalForm/index.ts 中可以看到useModalForm内部调用useForm并通过useModal管理弹窗的可见状态最终返回modal对象与useForm的全部返回值const useMantineFormResult useForm({ refineCoreProps, ...rest }); const { visible, show, close } useModal({ defaultVisible }); return { modal: { submit, close: handleClose, show: handleShow, visible, title, }, ...useMantineFormResult, saveButtonProps: { ...saveButtonProps, onClick: (e) onSubmit(submit)(e), }, };这意味着useModalForm继承了useForm的全部能力表单校验、getInputProps绑定、saveButtonProps、refineCore中的查询/变更逻辑等。官方文档明确说明useModalForm扩展自refinedev/mantine的useFormHook因此useForm的所有特性在useModalForm中同样可用详见 useForm 文档 与 useModalForm 完整文档。useModalForm的返回值由三部分构成返回值说明modal控制弹窗的状态与方法show/close/submit/visible/title/saveButtonPropsrefineCore核心useForm的返回值onFinish、id、setId、query、autoSaveProps等mantine/form的useForm返回值getInputProps、errors、reset、onSubmit等快速开始示例应用的整体结构仓库中的示例项目位于 examples/form-mantine-use-modal-form使用 Vite React Mantine Refine 构建。其目录结构如下examples/form-mantine-use-modal-form/ ├── src/ │ ├── components/ │ │ ├── table/ # 列排序、列过滤组件 │ │ ├── createPostModal.tsx │ │ ├── editPostModal.tsx │ │ └── index.ts │ ├── interfaces/index.d.ts # IPost / ICategory 类型定义 │ ├── pages/posts/list.tsx # 列表页承载两个弹窗表单 │ ├── App.tsx │ └── main.tsx ├── package.json └── vite.config.ts在 App.tsx 中应用通过refinedev/simple-rest接入https://api.fake-rest.refine.dev测试接口注册了posts资源并启用syncWithLocation与warnWhenUnsavedChanges两个全局选项Refine routerProvider{routerProvider} dataProvider{dataProvider(API_URL)} notificationProvider{useNotificationProvider} resources{[{ name: posts, list: /posts }]} options{{ syncWithLocation: true, warnWhenUnsavedChanges: true, }} 核心页面 pages/posts/list.tsx 同时声明了createModalForm与editModalForm两个表单实例并把它们分别传给独立的CreatePostModal与EditPostModal组件。接下来我们依次拆解三种操作模式。场景一create 模式在弹窗中新建记录在列表页中使用useModalForm并指定action: create。你不再需要单独的创建页面点击新建按钮即可在弹窗中填写并提交import { Box, Group, Modal, Pagination, ScrollArea, Select, Table, TextInput, } from mantine/core; import { List, SaveButton, useModalForm } from refinedev/mantine; import { useTable } from refinedev/react-table; import { ColumnDef, flexRender } from tanstack/react-table; import React from react; const PostList: React.FC () { const { getInputProps, saveButtonProps, modal: { show, close, title, visible }, } useModalForm({ refineCoreProps: { action: create }, initialValues: { title: , status: , content: , }, validate: { title: (value) (value.length 2 ? Too short title : null), status: (value) (value.length 0 ? Status is required : null), }, }); // ... useTable 列定义略 return ( Modal opened{visible} onClose{close} title{title} TextInput mt{8} labelTitle placeholderTitle {...getInputProps(title)} / Select mt{8} labelStatus placeholderPick one data{[ { label: Published, value: published }, { label: Draft, value: draft }, { label: Rejected, value: rejected }, ]} {...getInputProps(status)} / Box mt{8} sx{{ display: flex, justifyContent: flex-end }} SaveButton {...saveButtonProps} / /Box /Modal ScrollArea List createButtonProps{{ onClick: () show() }} Table highlightOnHover {/* ...表格渲染... */} /Table /List /ScrollArea / ); };关键点说明modal.visible绑定到 MantineModal的openedmodal.close绑定到onClosemodal.title直接作为弹窗标题create 模式下为 Create Post。getInputProps(title)会把mantine/form的取值、变更、错误信息一次性绑定到输入组件上validate中返回字符串即表示校验失败信息返回null表示通过。List组件的createButtonProps用于接管默认新建按钮的点击行为改为调用show()打开弹窗。saveButtonProps自带disabled校验未通过时禁用与onClick触发onSubmit(submit)直接传给SaveButton即可。场景二edit 模式编辑现有记录编辑模式与创建模式的区别在于refineCoreProps.action改为edit且show(id)需要接收记录的id。Refine 不会自动为列表中的每条记录添加EditButton因此需要你在表格的操作列中手动放置并调用show(getValue())把该行记录的 id 传入const { getInputProps, saveButtonProps, modal: { show, close, title, visible }, } useModalForm({ refineCoreProps: { action: edit }, initialValues: { title: , status: , content: , }, validate: { title: (value) (value.length 2 ? Too short title : null), status: (value) (value.length 0 ? Status is required : null), }, }); const columns React.useMemoColumnDefIPost[]( () [ // ...其他列 { id: actions, header: Actions, accessorKey: id, enableColumnFilter: false, enableSorting: false, cell: function render({ getValue }) { return ( Group spacingxs noWrap EditButton hideText onClick{() show(getValue() as number)} / /Group ); }, }, ], [], );官方文档特别强调必须把记录的id传给show编辑表单才能获取到对应记录的数据。show(id)内部会调用setId(id)随后refineCore中的useShow逻辑会按此 id 拉取记录并回填表单。这一步对edit和clone两种模式都是必需的。场景三clone 模式克隆一条记录克隆模式action: clone用于以某条记录为基础创建新记录表单会先加载被克隆记录的数据提交时则以创建create请求发送。代码结构几乎与 edit 模式一致只是按钮换成CloneButtonimport { CloneButton, List, SaveButton, useModalForm } from refinedev/mantine; const { getInputProps, saveButtonProps, modal: { show, close, title, visible }, } useModalForm({ refineCoreProps: { action: clone }, initialValues: { title: , status: , }, validate: { title: (value) (value.length 2 ? Too short title : null), status: (value) (value.length 0 ? Status is required : null), }, }); // 操作列 cell: function render({ getValue }) { return ( Group spacingxs noWrap CloneButton hideText onClick{() show(getValue() as number)} / /Group ); };同样地Refine 不会自动为每条记录添加CloneButton需要手动放置并把记录 id 传给show。组件化封装可复用的 CreatePostModal 与 EditPostModal示例项目没有把弹窗内联在列表页中而是抽成了独立组件。CreatePostModal接收useModalForm的完整返回值并通过UseModalFormReturnType泛型获得类型约束import type { BaseRecord, HttpError } from refinedev/core; import { type UseModalFormReturnType, useSelect, SaveButton, } from refinedev/mantine; import { Modal, TextInput, Select, Box, Text } from mantine/core; import MDEditor from uiw/react-md-editor; interface FormValues { title: string; content: string; status: string; category: { id: string }; } export const CreatePostModal: React.FC UseModalFormReturnTypeBaseRecord, HttpError, FormValues ({ getInputProps, errors, modal: { visible, close, title }, saveButtonProps, }) { const { selectProps } useSelect({ resource: categories, pagination: { mode: server }, }); return ( Modal opened{visible} onClose{close} title{title} TextInput mt{8} idtitle labelTitle placeholderTitle {...getInputProps(title)} / Select mt{8} idstatus labelStatus placeholderPick one {...getInputProps(status)} data{[ { label: Published, value: published }, { label: Draft, value: draft }, { label: Rejected, value: rejected }, ]} / Select mt{8} idcategoryId labelCategory placeholderPick one {...getInputProps(category.id)} {...selectProps} / Text mt{8} weight{500} sizesm color#212529 Content /Text MDEditor idcontent >const modalForm useModalForm({ refineCoreProps: { action: edit, resource: posts, id: 1, }, });当不显式传resource时Refine 会通过路由上下文自动推断当前资源。initialValues表单的默认值用于预填充展示数据。该属性只对create动作生效edit/clone模式下表单数据由记录加载而来const modalForm useModalForm({ initialValues: { title: Hello World, }, });defaultVisible是否默认打开弹窗默认falseconst modalForm useModalForm({ modalProps: { defaultVisible: true, }, });autoSubmitClose提交成功后是否自动关闭弹窗默认trueconst modalForm useModalForm({ modalProps: { autoSubmitClose: false, }, });autoResetForm提交成功后是否重置表单默认trueconst modalForm useModalForm({ modalProps: { autoResetForm: false, }, });autoResetFormWhenClose弹窗关闭时是否重置表单默认true。从 源码 可以看到该逻辑位于handleClose中关闭弹窗并setId(undefined)之后若此项为true则调用reset()从而避免下次打开弹窗时残留上次的数据const handleClose useCallback(() { // ... autoSave invalidate、warnWhen 确认逻辑 setId?.(undefined); close(); if (autoResetFormWhenClose) { reset(); } }, [warnWhen, autoSaveProps.status]);syncWithLocation当为true时弹窗的可见状态与记录的id会同步到 URL 查询参数中默认false。它还支持对象形式{ key: string; syncId?: boolean }来自定义查询参数的 key只有当syncId为true时id才会同步到 URLconst modalForm useModalForm({ syncWithLocation: { key: my-modal, syncId: true }, });从源码看默认的 key 格式为modal-${identifier}-${action}例如modal-posts-create。URL 同步由useParseduseGo配合两个useEffect完成首次加载时从parsed.params[key].open恢复弹窗开关状态、从parsed.params[key].id恢复记录 id此后每当visible或id变化就通过go({ query: { [key]: { open, id } }, type: replace, options: { keepQuery: true } })写入 URL关闭弹窗时则移除该参数。overtimeOptions用于请求超时提醒interval为轮询间隔毫秒onInterval为每个间隔触发的回调。Hook 返回overtime对象elapsedTime为已耗时毫秒请求完成后变为undefinedconst { overtime } useModalForm({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000, 4000, ... // 使用示例请求超过 4 秒时提示 { elapsedTime 4000 divthis takes a bit longer than expected/div; }autoSave自动保存功能用于编辑时自动提交表单。通过refineCoreProps.autoSave配置其子项如下子项说明默认值enabled是否启用自动保存falsedebounce防抖时间毫秒1000invalidateOnUnmount卸载时是否失效当前资源的list/many/detail查询falseinvalidateOnClose关闭弹窗时是否失效当前资源的查询falseuseModalForm({ refineCoreProps: { autoSave: { enabled: true, debounce: 2000, // 防抖 2 秒 invalidateOnUnmount: true, // 卸载时失效查询 invalidateOnClose: true, // 关闭时失效查询 }, }, });官方文档明确指出autoSave 仅在 edit 模式下工作。编辑数据时改动会自动保存而创建数据时仍需手动点击保存。失效的查询集合可以通过invalidates属性选择。onMutationSuccess与onMutationError回调会在变更成功或失败后触发可通过isAutoSave参数判断变更是否由自动保存触发。此外从源码可见invalidateOnClose的实际逻辑是在handleClose中当autoSaveProps.status success且autoSave?.invalidateOnClose时调用invalidate({ invalidates: invalidates || [list, many, detail], resource: identifier, ... })完成数据失效。返回值详解useModalForm除了继承useForm与mantine/form的全部返回值外还额外提供以下内容返回值说明modal.visible弹窗当前可见状态booleanmodal.title弹窗标题基于资源与动作自动生成modal.close关闭弹窗的函数modal.submit手动提交表单的函数modal.show打开弹窗的函数可接收idsaveButtonProps提交按钮所需 propsdisabled、onClick等overtime超时信息{ elapsedTime?: number }autoSaveProps自动保存的变更结果{ data, error, status }title根据资源与动作自动生成title通过useTranslate与useUserFriendlyName生成。源码中的实现为优先查找translate(${identifier}.titles.${actionProp})否则回退到${actionProp} ${getUserFriendlyName(label, singular)}例如 create posts 资源得到 Create Postconst { modal: { title }, } useModalForm({ refineCoreProps: { resource: posts, action: create, }, }); console.log(title); // Create Postclose手动关闭弹窗当需要自定义关闭行为时可以直接调用modal.closeModal opened{visible} onClose{close} title{title} {/* ...表单字段... */} Box mt{8} sx{{ display: flex, justifyContent: flex-end }} SaveButton {...saveButtonProps} / Button onClick{close}Cancel/Button /Box /Modal注意modal.close实际对应源码中的handleClose它会处理自动保存失效、未保存更改确认warnWhenUnsavedChanges通过window.confirm提示、setId(undefined)以及autoResetFormWhenClose重置等逻辑。submit手动提交表单Button onClick{submit}Save/Button源码中submit的实现为await onFinish(values)后若autoSubmitClose为true则close()若autoResetForm为true则reset()。show打开弹窗show(id?)接收可选 id。在handleShow的实现中若传入 id 则先setId(showId)对于edit/clone模式只有存在 id 时才允许打开弹窗needsIdToOpen ? hasId : true从机制上保证了编辑/克隆表单一定有数据可加载const handleShow useCallback( (showId?: BaseKey) { if (typeof showId ! undefined) { setId?.(showId); } const needsIdToOpen action edit || action clone; const hasId typeof showId ! undefined || typeof id ! undefined; if (needsIdToOpen ? hasId : true) { show(); } }, [id], );saveButtonProps自定义提交按钮saveButtonProps包含提交按钮需要的全部属性disabled、loading等。当需要加入自定义逻辑时可以包装其onClickconst { getInputProps, modal, saveButtonProps } useModalForm(); return ( Modal {...modal} TextInput mt{8} labelTitle placeholderTitle {...getInputProps(title)} / Box mt{8} sx{{ display: flex, justifyContent: flex-end }} Button {...saveButtonProps} onClick{(e) { // -- your custom logic saveButtonProps.onClick(e); }} / /Box /Modal );overtime 与 autoSavePropsovertime.elapsedTime在请求进行中每隔interval更新一次请求完成后为undefined启用 autoSave 后autoSaveProps返回变更请求的data、error与statusloading | error | idle | success。FAQ提交前转换表单数据如果需要在数据发送到 API 之前改写表单值可以使用transformValues。例如把两个输入框name与surname合并为fullName发送import React from react; import { useModalForm } from refinedev/mantine; import { TextInput, Modal, Button, Box } from mantine/core; const UserCreate: React.FC () { const { getInputProps, saveButtonProps, modal: { show, close, title, visible }, } useModalForm({ refineCoreProps: { action: create }, initialValues: { name: , surname: , }, transformValues: (values) ({ fullName: ${values.name} ${values.surname}, }), }); return ( Modal opened{visible} onClose{close} title{title} TextInput mt{8} labelName placeholderName {...getInputProps(name)} / TextInput mt{8} labelSurname placeholderSurname {...getInputProps(surname)} / Box mt{8} sx{{ display: flex, justifyContent: flex-end }} Button {...saveButtonProps} onClick{(e) { // -- your custom logic saveButtonProps.onClick(e); }} / /Box /Modal ); };transformValues是mantine/form的能力转换结果会作为 mutation 的入参发送给数据提供者。API 参考速查PropertiesProperty说明类型modalProps弹窗配置对象ModalPropsTyperefineCoreProps核心useForm的配置对象UseFormPropsmantine/form的useForm属性参见 useForm 文档—ModalPropsTypeProperty说明类型默认值defaultVisible弹窗初始可见状态booleanfalseautoSubmitClose提交后是否自动关闭弹窗booleantrueautoResetForm提交后是否重置表单booleantrueautoResetFormWhenClose关闭时是否重置表单booleantrueType Parameters参数说明默认值TQueryFnData查询函数返回的数据类型继承BaseRecordBaseRecordTError自定义错误对象继承HttpErrorHttpErrorTVariables变更函数的表单值类型Recordstring, unknownTTransformed转换后的表单值类型TVariablesTDataselect函数返回的数据类型继承BaseRecordTQueryFnDataTResponse变更函数返回的数据类型继承BaseRecordTDataTResponseError自定义错误对象继承HttpErrorTErrorReturn Values属性说明类型modal控制弹窗的状态与方法ModalReturnValuesrefineCore核心useForm的返回值UseFormReturnValuesmantine/form的useForm返回值参见 useForm 文档—overtime超时加载信息{ elapsedTime?: number }autoSaveProps自动保存信息{ data?, error, status }ModalReturnValues属性说明类型visible弹窗可见状态booleanshow打开弹窗可传 id(id?: BaseKey) voidclose关闭弹窗() voidsubmit提交表单(values: TVariables) voidtitle基于资源与动作生成的标题stringsaveButtonProps提交按钮 props{ disabled: boolean, onClick: (e) void }小结useModalForm把 Refine 的表单能力与 Mantine 的弹窗组件无缝衔接create/edit/clone三种模式只需切换refineCoreProps.action配合show(id)传入记录 id 即可完成数据加载与提交syncWithLocation让弹窗状态可被 URL 还原与分享autoSave、overtimeOptions等选项进一步覆盖了自动保存与慢请求提示等真实业务场景。完整的可运行示例位于 examples/form-mantine-use-modal-form对应的 Hook 实现源码在 packages/mantine/src/hooks/form/useModalForm/index.ts测试用例见 index.spec.tsx可作为深入理解与二次开发的起点。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考