【问题标题】:React Formik use submitForm outside <Formik />React Formik 在 <Formik /> 之外使用 submitForm
【发布时间】: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 操作系统

【问题讨论】:

    标签: reactjs formik


    【解决方案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://stackoverflow.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 <></>
               }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-03-08
                • 2019-04-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-01-30
                • 1970-01-01
                相关资源
                最近更新 更多