You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-admin 4中SimpleForm工具栏无法获取isValid,useFormState报错

问题:react-admin 4自定义Toolbar中useFormState报错,无法根据表单有效性禁用SaveButton

react-admin 4版本中,Toolbar的valid、invalid等属性已被移除,转而使用react-hook-form钩子。在使用相关示例代码时,调用useFormState()出现如下报错:

Uncaught TypeError TypeError: Cannot read properties of null (reading 'control')

自定义Toolbar代码

import { Button } from '@mui/material'
import React, { memo } from 'react'
import { SaveButton, Toolbar, useTranslate } from 'react-admin'
import { useFormState } from 'react-hook-form'

/**
 * Save + cancel + delete buttons for a react-admin create/edit form
 */
const SaveCancelDeleteToolbar = (props: any) => {
  const { className, handleCancel } = props

  const translate = useTranslate()

  const { isValid } = useFormState()

  return (
    <Toolbar className={className}>
      <SaveButton disabled={!isValid} onClick={handleCancel} />
      <Button onClick={handleCancel}>{translate('ra.action.cancel')}</Button>
    </Toolbar>
  )
}

export default memo(SaveCancelDeleteToolbar)

补充分析

已知通过const {control} = useForm()会创建新表单实例,即便能规避报错,也无法获取当前表单的真实状态。根据useFormState的规则,只有在FormContext作用域内时,useFormState的control参数才是可选的。目前推测问题根源是useFormContext()返回null,导致无法获取表单上下文。

组件调用关系

SimpleForm中调用Toolbar的代码

const AnyForm = (props: CreateEditFormProps) => {
  const { label1, label2, label3, save, source1, source2, source3, toolbar } =
    props

  return (
    <SimpleForm mode="onBlur" toolbar={toolbar} onSubmit={save}>
      <TextInput
        label={label1}
        source={source1}
        validate={[required(), maxLength(50), noEmptySpace]}
      />
      <TextInput
        label={label2}
        source={source2}
        validate={[required(), maxLength(3), minLength(3), noEmptySpace]}
      />
      <TextInput
        label={label3}
        source={source3}
        validate={[required(), maxLength(50), noEmptySpace]}
      />
    </SimpleForm>
  )
}

Toolbar传递给表单的逻辑

const CreateButtonWithDrawer = (props:any) => {
    const {allMyProps} = props

    // Styles, Access, Title, handleCancel functions

    const memoizedToolbar = useMemo(
        () => <SaveCancelDeleteToolbar handleCancel={handleCancel} />,
        [handleCancel]
    )

    return (
      <Drawer
        anchor="right"
        aria-label={finalTitle}
        className={classes.drawer}
        open={isCreating}
        transitionDuration={drawerTransitionDuration}
        onClose={modalCancel}
      >
          <CreateDrawerContent
              basepath={basepath}
              classes={classes}
              closeDrawer={closeDrawer}
              pristinecallback={pristineCallback}
              resourceParam={resource}
              title={finalTitle}
              toolbar={memoizedToolbar}
              onSuccess={onSuccess}
          >
                {children}
          </CreateDrawerContent>
      </Drawer>
    )
}

const CreateDrawerContent = (props:any) => {
    const {allMyProps} = props

    const controllerProps = useCreateController({
        mutationOptions: { onSuccess },
        redirect: false,
        resource,
      })

    const { record, save, saving } = controllerProps
    
    return (
        <CreateContextProvider value={controllerProps}>
        <ResourceContextProvider value={resource}>
          {cloneElement(children, {
            closeDrawer,
            toolbar,
            // other props
            undoable: false,
          })}
        </ResourceContextProvider>
      </CreateContextProvider>
    )

}

在List操作中调用组件

const AnyListActions = (props: ListActionProps) => {
  const { referenceRecord, target } = props

  const memoizedCustomActions = useMemo(
    () => [
      <CreateButtonWithDrawer key="createAction">
        <AnyForm
          referenceRecord={referenceRecord}
          target={target}
        />
      </CreateButtonWithDrawer>,
    ],
    [referenceRecord, target]
  )

  return (
    <ListFilterCustomExportActions
      customActions={memoizedCustomActions}
      exporter={false}
    />
  )
}

export default memo(AnyListActions)

已尝试的无效解决方案

  • 更新相关包版本
  • 使用FormDataConsumer获取表单值,但需要为每个资源重新编写验证逻辑,无法复用Toolbar
  • 调用useCreateContext或useEditContext获取上下文信息,但其中不包含isValid状态
  • 调用useAugmentedForm钩子
  • 在SimpleForm中添加validate或resolver属性
  • 直接以TSX形式调用Toolbar
  • 在子组件中调用Toolbar并设置toolbar={false}

需求

实现当SimpleForm处于无效状态时,自动禁用SaveButton。

内容的提问来源于stack exchange,提问作者Lucas PARISOT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 01:57:08