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

Posted

技术标签:

【中文标题】React Formik 在 <Formik /> 之外使用 submitForm【英文标题】:React Formik use submitForm outside <Formik /> 【发布时间】:2018-09-06 13:56:39 【问题描述】:

当前行为

<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,但它总是返回给我Uncaught TypeError: _this.props.onSubmit is not a function

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

有什么解决办法吗?


- Formik 版本:最新 - 反应版本:v16 - 操作系统:Mac 操作系统

【问题讨论】:

【参考方案1】:

仅供想知道通过 React hooks 解决什么问题的人使用:

Formik 2.x,如this 回答中所述

// 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 属性。

TLDR ;如果您提交的组件是 &lt;Formik&gt;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 />

【讨论】:

Formik 现在是一个功能组件 这就是为什么 Formik 组件有一个 innerRef 属性,如果我是正确的,传递你想要附加的引用:github.com/jaredpalmer/formik/blob/… 在 TypeScript 中使用 useRef 不要忘记设置 FormikValues 类型 useRef&lt;FormikValues&gt;() 简单最好的解决方案 Formik 1.5.x 的最佳解决方案。 Formik 2.x,见ZEE发布的解决方案【参考方案2】:

您可以将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;

【讨论】:

函数如何做到这一点?不太清楚如何使用书籍。 @iwaduarte 你的意思是“钩子”:-)?对我来说,这看起来是一个很好的 useRef 案例 添加了“钩子”版本的答案:) 看起来很糟糕。如果这不是内部方法就好了。 这是一种很老套的做法。我建议你使用来自 formik 的ref 并在你需要的地方使用它【参考方案3】:

这里描述了我找到的最佳解决方案https://***.com/a/53573760

在此复制答案:

在表单中添加“id”属性:id='my-form'

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

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

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

现在,“外部按钮”按钮将与表单内部完全等效。

注意:IE11 不支持此功能。

【讨论】:

【参考方案4】:

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

这个问题可以通过简单的html 来解决。如果您在form 上放置了id 标记,那么您可以使用按钮的form 标记将其定位到按钮。

示例:

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

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

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

【讨论】:

【参考方案5】:

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

   const  values, submitForm  = useFormikContext();

PS:这仅适用于那些实际上不需要在 Formik 组件之外调用 submit 的人,因此您可以将 Formik 组件置于更高级别而不是使用 ref在组件树中,并使用自定义钩子useFormikContext,但如果真的需要从Formik外部提交,则必须使用useRef

<Formik innerRef=formikRef />

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

【讨论】:

另外,值得注意的是 useFormikContext 仅存在于 Formik 2.x 在树下,ref 允许你使用外部 Formik 组件吗? 我认为首选的方法是使用 withFormik HoC 包装将提交的组件 --> formik.org/docs/api/withFormik 以便能够调用 useFormikContext。 为什么?如果您的根组件是 Formik,则不需要 withFormik 问题是一个6字的句子。一是“外”。你怎么能认为这是一个正确的答案?【参考方案6】:

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

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

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

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

您将&lt;FormContextProvider&gt; 放置在您的应用程序中足够高的位置,以便它包装需要访问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>

在呈现&lt;Formik&gt; 元素的组件中,我添加了这一行:

const  formikFormRef  = useContext(FormContext)

在同一个组件中,我将此属性添加到&lt;Formik&gt; 元素:

innerRef=formikFormRef

在同一个组件中,嵌套在 &lt;Formik&gt; 元素下的第一件事就是 this(重要的是,注意添加了 &lt;FormContextRefreshConduit /&gt; 行)。

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

在包含提交/重置按钮的组件中,我有以下内容。注意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 是一种可行的解决方法。

谢谢,我从其他答案中汲取灵感,想找到一种方法来满足我自己的所有要求。

【讨论】:

forceUpdate 的补充非常好。这就是innerRef 的问题——它不会触发父组件中的重新渲染。您在使用它来公开 Formik 方法(例如 submitFormresetForm)与使用它来观察状态(例如 dirtyisSubmitting)之间的区别很明显。很有帮助。【参考方案7】:

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

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

只需在您的表单上放置一个常规的 react ref:

  <form
        ref=newFormRef
        
        onSubmit=handleSubmit
      >

【讨论】:

【参考方案8】:

找到了罪魁祸首。

Formik 道具上不再有onSubmitCallback。应该改成onSubmit

【讨论】:

【参考方案9】:

你可以试试

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

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

【讨论】:

【参考方案10】:

另一种简单的方法是使用状态并将 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 Formik 在 <Formik /> 之外使用 submitForm的主要内容,如果未能解决你的问题,请参考以下文章

如何在 react-apollo-hooks 和 formik 中使用突变?

React Formik:如何使用自定义 onChange 和 onBlur

React Formik Field onChange 事件句柄

如何在 React 中使用 Formik 的自定义输入?

清除带有初始值 React 的 Formik 字段

如何使用formik react js在handlesubmit函数中接收选择值?