React Formik 在 <Formik /> 之外使用 submitForm

React Formik use submitForm outside <Formik />

当前行为

<Formik
    isInitialValid
    initialValues={{ first_name: 'Test', email: 'test@mail.com' }}
    validate={validate}
    ref={node => (this.form = node)}
    onSubmitCallback={this.onSubmitCallback}
    render={formProps => {
        const fieldProps = { formProps, margin: 'normal', fullWidth: true, };
        const {values} = formProps;
        return (
            <Fragment>
                <form noValidate>
                    <TextField
                        {...fieldProps}
                        required
                        autoFocus
                        value={values.first_name}
                        type="text"
                        name="first_name"

                    />

                    <TextField
                        {...fieldProps}
                        name="last_name"
                        type="text"
                    />

                    <TextField
                        {...fieldProps}
                        required
                        name="email"
                        type="email"
                        value={values.email}

                    />
                </form>
                <Button onClick={this.onClick}>Login</Button>
            </Fragment>
        );
    }}
/>

我正在尝试这个解决方案 https://github.com/jaredpalmer/formik/issues/73#issuecomment-317169770 但它总是 return 我 Uncaught TypeError: _this.props.onSubmit is not a function

当我尝试 console.log(this.form) 时,有 submitForm 功能。

大家有什么解决办法吗?


- Formik版本:最新 -反应版本:v16 - OS: Mac OS

找到了罪魁祸首。

Formik 道具不再有 onSubmitCallback。应将其更改为 onSubmit

您可以将 formikProps.submitForm(Formik 以编程方式提交)绑定到父组件,然后从父组件触发提交:

import React from 'react';
import { Formik } from 'formik';

class MyForm extends React.Component {
    render() {
        const { bindSubmitForm } = this.props;
        return (
            <Formik
                initialValues={{ a: '' }}
                onSubmit={(values, { setSubmitting }) => {
                    console.log({ values });
                    setSubmitting(false);
                }}
            >
                {(formikProps) => {
                    const { values, handleChange, handleBlur, handleSubmit } = formikProps;

                    // bind the submission handler remotely
                    bindSubmitForm(formikProps.submitForm);

                    return (
                        <form noValidate onSubmit={handleSubmit}>
                            <input type="text" name="a" value={values.a} onChange={handleChange} onBlur={handleBlur} />
                        </form>
                    )
                }}
            </Formik>
        )
    }
}

class MyApp extends React.Component {

    // will hold access to formikProps.submitForm, to trigger form submission outside of the form
    submitMyForm = null;

    handleSubmitMyForm = (e) => {
        if (this.submitMyForm) {
            this.submitMyForm(e);
        }
    };
    bindSubmitForm = (submitForm) => {
        this.submitMyForm = submitForm;
    };
    render() {
        return (
            <div>
                <button onClick={this.handleSubmitMyForm}>Submit from outside</button>
                <MyForm bindSubmitForm={this.bindSubmitForm} />
            </div>
        )
    }
}

export default MyApp;

仅供想知道通过 React hooks 解决方案的人:

Formik 2.x, as explained in answer

// import this in the related component
import { useFormikContext } from 'formik';

// Then inside the component body
const { submitForm } = useFormikContext();

const handleSubmit = () => {
  submitForm();
}

请记住,该解决方案仅适用于 Formik 组件内部 组件,因为它使用上下文 API。如果出于某种原因你想从外部组件手动提交,或者从实际使用 Formik 的组件提交,你实际上仍然可以使用 innerRef prop.

TLDR;如果您提交的组件是 <Formik>withFormik() 组件的子组件,则此上下文答案就像一个魅力,否则,请使用下面的 innerRef 答案。

Formik 1.5.x+

// Attach this to your <Formik>
const formRef = useRef()

const handleSubmit = () => {
  if (formRef.current) {
    formRef.current.handleSubmit()
  }
}

// Render
<Formik innerRef={formRef} />

如果您将 class 组件转换为功能组件,自定义挂钩 useFormikContext 提供了一种在树下的任何位置使用提交的方法:

   const { values, submitForm } = useFormikContext();

PS:这只适用于那些不需要在 Formik 组件之外调用提交的人,所以不要使用 ref可以将你的 Formik 组件放在组件树的更高级别,并使用自定义钩子 useFormikContext,但如果真的需要从外部提交 Formik 你将不得不使用 useRef .

<Formik innerRef={formikRef} />

https://formik.org/docs/api/useFormikContext

此处描述了我找到的最佳解决方案

在此处复制答案:

向您的表单添加“id”属性:id='my-form'

class CustomForm extends Component {
    render() {
        return (
             <form id='my-form' onSubmit={alert('Form submitted!')}>
                // Form Inputs go here    
             </form>
        );
    }
}

然后在表单外的目标按钮的“表单”属性中添加相同的Id:

<button form='my-form' type="submit">Outside Button</button>

现在,'Outside Button' 按钮将完全等同于在表单内部。

注意:IE11不支持。

如果您使用的是 withFormik,这对我有用:

  const handleSubmitThroughRef = () => {
    newFormRef.current.dispatchEvent(
      new Event("submit", { cancelable: true, bubbles: true })
    );
  };

只需在您的表单上放置一个常规的 React 参考:

  <form
        ref={newFormRef}
        
        onSubmit={handleSubmit}
      >

我刚刚遇到了同样的问题,并找到了一个非常简单的解决方案,希望对您有所帮助:

这个问题可以用简单的 html 来解决。如果您在 form 上放置了 id 标签,那么您可以使用按钮的 form 标签将其作为按钮的目标。

示例:

      <button type="submit" form="form1">
        Save
      </button>
      <form onSubmit={handleSubmit} id="form1">
           ....
      </form>

您可以将表单和按钮放置在任何地方,甚至分开放置。

然后此按钮将触发表单提交功能,并且 formik 将捕获该功能并照常继续该过程。 (只要在呈现按钮的同时在屏幕上呈现表单,那么无论表单和按钮位于何处,这都将起作用)

你可以试试

const submitForm = ({ values, setSubmitting }) =>{//...在这里做点什么}

submitForm({values, setSubmitting})> {()=>(//...在这里做点什么)}

2021年,使用16.13.1,这种方式对我来说满足了几个要求:

  • submit/reset 按钮不能嵌套在 <Formik> 元素中。请注意,如果你能做到这一点,那么你应该使用 useFormikContext 答案,因为它比我的更简单。 (我的将允许您更改正在提交的表单(我有一个应用栏,但用户可以导航到多个表单)。
  • 外部 submit/reset 按钮必须能够提交和重置 Formik 表单。
  • 外部 submit/reset 按钮必须显示为禁用,直到表单变脏(外部组件必须能够观察 Formik 表单的 dirty 状态。)

这是我的想法:我创建了一个新的上下文提供程序,专门用于将一些有用的 Formik 内容保存到 link 我的两个外部组件,它们位于应用程序的不同嵌套分支中(全局应用程序栏和其他地方的表单,在页面视图的更深处——事实上,我需要 submit/reset 按钮来适应用户导航到的不同表单,而不仅仅是一个;不仅仅是一个 <Formik> 元素,而是只有一次一个)。

以下示例使用 TypeScript,但如果您只知道 javascript,请忽略冒号后的内容,在 JS 中也是如此。

您将 <FormContextProvider> 放置在您的应用程序中足够高的位置,以便它包装需要访问 Formik 内容的两个不同组件。简化示例:

<FormContextProvider>
  <MyAppBar />
  <MyPageWithAForm />
</FormContextProvider>

这是 FormContextProvider:

import React, { MutableRefObject, useRef, useState } from 'react'
import { FormikProps, FormikValues } from 'formik'

export interface ContextProps {
  formikFormRef: MutableRefObject<FormikProps<FormikValues>>
  forceUpdate: () => void
}

/**
 * Used to connect up buttons in the AppBar to a Formik form elsewhere in the app
 */
export const FormContext = React.createContext<Partial<ContextProps>>({})

// https://github.com/deeppatel234/react-context-devtool
FormContext.displayName = 'FormContext'

interface ProviderProps {}

export const FormContextProvider: React.FC<ProviderProps> = ({ children }) => {
  // Note, can't add specific TS form values to useRef here because the form will change from page to page.
  const formikFormRef = useRef<FormikProps<FormikValues>>(null)
  const [refresher, setRefresher] = useState<number>(0)

  const store: ContextProps = {
    formikFormRef,
    // workaround to allow components to observe the ref changes like formikFormRef.current.dirty
    forceUpdate: () => setRefresher(refresher + 1),
  }

  return <FormContext.Provider value={store}>{children}</FormContext.Provider>
}

在呈现 <Formik> 元素的组件中,我添加了这一行:

const { formikFormRef } = useContext(FormContext)

在同一组件中,我将此属性添加到 <Formik> 元素:

innerRef={formikFormRef}

在同一组件中,第一个嵌套在 <Formik> 元素下的是这个(重要的是,请注意添加了 <FormContextRefreshConduit /> 行)。

<Formik
  innerRef={formikFormRef}
  initialValues={initialValues}
  ...
>
  {({ submitForm, isSubmitting, initialValues, values, setErrors, errors, resetForm, dirty }) => (
    <Form>
      <FormContextRefreshConduit />
      ...

在包含 submit/reset 按钮的组件中,我有以下内容。注意 formikFormRef

的使用
export const MyAppBar: React.FC<Props> = ({}) => {
  const { formikFormRef } = useContext(FormContext)
  
  const dirty = formikFormRef.current?.dirty

  return (
    <>
      <AppButton
        onClick={formikFormRef.current?.resetForm}
        disabled={!dirty}
      >
        Revert
      </AppButton>
      <AppButton
        onClick={formikFormRef.current?.submitForm}
        disabled={!dirty}
      >
        Save
      </AppButton>
    </>
  )
}

ref 对于调用 Formik 方法很有用,但通常无法观察到它的 dirty 属性(react 不会为此更改触发重新渲染). FormContextRefreshConduitforceUpdate 一起是可行的解决方法。

谢谢,我从其他答案中得到启发,找到了满足我自己所有要求的方法。

另一种简单的方法是使用State 并将prop 传递给子formik 组件。在那里您可以使用 useEffect 挂钩设置状态。

const ParentComponent = () => {
  const [submitFunc, setSubmitFunc] = useState()
  return <ChildComponent setSubmitFunc={setSubmitFunc}>
}

const ChildComponent= ({ handleSubmit, setSubmitFunc }) => {
   useEffect(() => {
     if (handleSubmit) setSubmitFunc(() => handleSubmit)
   }, [handleSubmit, setSubmitFunc])

   return <></>
 }

我已经在 React Class 组件中逐步实现了它:

1 - 我已经声明了一个“ref”变量来保持对表单对象的引用,(useRef 仅在函数组件中有效,所以我使用 React.createRef() 函数进行了如下编码)

constructor(props) {
  super(props);
  this.visitFormRef = React.createRef();
}

2 - formik 表单上有一个“innerRef”功能,因此我已将上面的 ref 变量分配给它:

<Formik 
    initialValues={initialValues}
    onSubmit={(values) => onSubmit(values)}
    validationSchema={validationSchema}
    enableReinitialize={true}
    innerRef={this.visitFormRef}  //<<--here
>

3- 为了触发表单的提交事件,从表单外的某个地方我在下面声明了一个函数:

triggerFormSubmit = () => {
    
    if (this.visitFormRef.current) 
        this.visitFormRef.current.handleSubmit();
}

4- 最后我从外部按钮调用了上面的函数:

<Button onClick={() => this.triggerFormSubmit()} />

注意不要混淆:关联到 formik 表单的 onSubmit(values) 函数仍然存在,并且它正在获取 from 值。我们刚刚从此处的外部按钮触发了它