React Formik use submitForm outside <Formik />
Asked Answered
O

12

90

Current Behavior

<Formik
    isInitialValid
    initialValues={{ first_name: 'Test', email: '[email protected]' }}
    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>
        );
    }}
/>

I'm trying this solution https://github.com/jaredpalmer/formik/issues/73#issuecomment-317169770 but it always return me Uncaught TypeError: _this.props.onSubmit is not a function

When I tried to console.log(this.form) there is submitForm function.

Any solution guys?


- Formik Version: latest - React Version: v16 - OS: Mac OS

Overside answered 28/3, 2018 at 2:18 Comment(0)
A
117

Just for anyone wondering what's the solution via React hooks :

Formik 2.x, as explained in this answer

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

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

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

Keep in mind that solution only works for components inside a Formik component as this uses the context API. If for some reason you'd like to manually submit from an external component, or from the component the Formik is actually used from, you can actually still use the innerRef prop.

TLDR ; This context answers works like a charm if the component that you're submitting from is a child of a <Formik> or withFormik() component, otherwise, use the innerRef answer below.

Formik 1.5.x+

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

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

// Render
<Formik innerRef={formRef} />
Agist answered 3/10, 2019 at 16:58 Comment(10)
Formik now is a functional componentCony
That's why the Formik component has an innerRef prop, to pass down the ref that you want to attach if i'm correct : github.com/jaredpalmer/formik/blob/…Agist
Using useRef with TypeScript don't forget to set FormikValues type useRef<FormikValues>()Kerge
simple and best solutionDemission
Best solution for Formik 1.5.x . For Formik 2.x, see the solution posted by ZEECritter
Updated answer with more details on @zee 's answer.Agist
Using useRef with TypeScript I have found the correct way in 2021 is useRef<FormikProps<FormikValues>>(null)Aestivation
It seems that innerRef is not working with Formik 1.5.x. I keep getting {current: null} for the reference as in the example.Housing
I am using useFormik hook and I want to trigger submission from the footer of my modal that is outside of <form onSubmit={formik.handleSubmit}/> how can I do this with useFormik() hook ?? @EricMartinNamara
You'd need to make a ref in a parent component to the form and footer, pass it down to the <Formik> component, and to the footer of your modal for usage @HaseebIrfanAgist
S
42

You can bind formikProps.submitForm (Formik's programatic submit) to a parent component and then trigger submission from the parent:

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;
Supercilious answered 19/11, 2018 at 23:5 Comment(8)
How would do that with functions? Not quite sure how to approach using books.Fisher
@Fisher you mean "hooks" :-) ? That looks like a good case for a useRef to meKhz
Added an answer for the "hook" version :)Agist
Seems pretty crappy. Would be nice if this wasn't an internal method.Most
This worked for me, but when handleSubmitMyForm gets callde, it's not triggering my Yup validation. How can I make this work?Vignette
This is way to hacky to do. I suggest you use ref from formik and use it where you needCreepy
I have ported this example to using function componets + hooks, makes sense or rather solutions proposed in answers below? codesandbox.io/s/boring-visvesvaraya-kuck2?file=/src/MyForm.tsxSpradlin
+1 This seems to be the only answer to the actual question, which is to get a hold on submitForm() (which is very different from 'submitting form manually' or 'calling handleSubmit)Yuletide
B
30

I just had the same issue and found a very easy solution for it hope this helps:

The issue can be solved with plain html. If you put an id tag on your form then you can target it with your button using the button's form tag.

example:

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

You can place the form and the button anywhere even separate.

This button will then trigger the forms submit functionality and formik will capture that continue the process as usual. (as long as the form is rendered on screen while the button is rendered then this will work no matter where the form and the button are located)

Brotherly answered 8/4, 2021 at 14:29 Comment(2)
Any disadvantages to this? Seems like the best solutionIrmine
Pretty nice and clean solution!Alphitomancy
N
24

The best solution I've found is described here https://stackoverflow.com/a/53573760

Copying the answer here:

Add "id" attribute to your form: id='my-form'

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

Then add the same Id to the "form" attribute of the target button outside of the form:

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

Now, the 'Outside Button' button will be absolutely equivalent as if it is inside the form.

Note: This is not supported by IE11.

Nuncupative answered 28/12, 2020 at 13:43 Comment(0)
O
5

If you convert your class component to a functional component, the custom hook useFormikContext provide a way to use submit anywhere down the tree:

   const { values, submitForm } = useFormikContext();

PS: this only for those who don't really need to call submit outside the Formik Component, so instead of using a ref you can put your Formik component at a higher level in the component tree, and use the custom hook useFormikContext, but if really need to submit from outside Formik you'll have to use a useRef .

<Formik innerRef={formikRef} />

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

Occupational answered 5/7, 2020 at 2:57 Comment(7)
Also, it's worth noting that useFormikContext exists only at Formik 2.xCritter
down the tree, the ref allows you to use outside Formik component no?Dramatization
i think the preferred way is to wrap the component which will do the submit with withFormik HoC --> formik.org/docs/api/withFormik in order to be able to call the useFormikContext.Sapp
Why? You don't need withFormik if your root component is FormikOccupational
what happened if submit button outside the component?Deeann
The question is a 6 words sentence. One is "outside". How could you consider this as a correct answer?Ogrady
This answer gave me a lot more contextIrmine
A
5

In 2021, using 16.13.1, this way worked for me to satisfy several requirements:

  • The submit/reset buttons cannot be nested within the <Formik> element. Note, if you can do this then you should use the useFormikContext answer because it is simpler than mine. (Mine will allow you to change which form is being submitted (I have one app bar but multiple forms the user can navigate to).
  • The external submit/reset buttons must be able to submit and reset the Formik form.
  • The external submit/reset buttons must appear disabled until the form is dirty (the external component must be able to observe the Formik form's dirty state.)

Here's what I came up with: I created a new context provider dedicated to holding some helpful Formik stuff to link my two external components which are in different nested branches of the app (A global app bar and a form somewhere else, deeper in a page view – in fact, I need the submit/reset buttons to adapt to different forms the user has navigated to, not just one; not just one <Formik> element, but only one at a time).

The following examples use TypeScript but if you only know javascript just ignore the stuff after colons and it's the same in JS.

You place <FormContextProvider> high enough in your app that it wraps both of the disparate components that need to have access to Formik stuff. Simplified example:

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

Here's 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>
}

In the component that renders the <Formik> element, I add this line:

const { formikFormRef } = useContext(FormContext)

In the same component, I add this attribute to the <Formik> element:

innerRef={formikFormRef}

In the same component, the first thing nested under the <Formik> element is this (importantly, note the addition of the <FormContextRefreshConduit /> line).

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

In my component that contains the submit/reset buttons, I have the following. Note the use of 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>
    </>
  )
}

The ref is useful for calling Formik methods but is not normally able to be observed for its dirty property (react won't trigger a re-render for this change). FormContextRefreshConduit together with forceUpdate are a viable workaround.

Thank you, I took inspiration from the other answers to find a way to meet all of my own requirements.

Aestivation answered 17/8, 2021 at 0:52 Comment(4)
great call on the forceUpdate addition. That's the catch with innerRef - it won't trigger rerenders in parent components. Your distinction between using it to expose Formik methods (e.g. submitForm or resetForm) vs. using it to observe state (e.g. dirty or isSubmitting) is spot on. Very helpful.Iceni
@ABCD.ca, when your Formik renders, it calls its rendering function, which renders <FormContextRefreshConduit/> which uses FormContext to call forceUpdate which changes the state of the FormContext. How do you not get Warning: Cannot update a component (FormContextProvider) while rendering a different component (Formik).Diecious
@Diecious Sorry, I don't know but it doesn't give me that error – something about it being in a different scope seems to helpAestivation
@Aestivation is there a way to get a runnable link/code for your method? I'm following a similar approach but getting the error message I quoted above in the console (at run-time).Diecious
C
3

If you're using withFormik, this worked for me:

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

Just put a regular react ref on your form:

  <form
        ref={newFormRef}
        
        onSubmit={handleSubmit}
      >
Cherice answered 24/3, 2021 at 11:3 Comment(0)
O
0

found the culprit.

There are no longer onSubmitCallback on Formik props. Should change it to onSubmit

Overside answered 28/3, 2018 at 5:12 Comment(0)
E
0

You can try it

const submitForm = ({ values, setSubmitting }) =>{//...do something here}

<Formik onSubmit={(values, {setSubmitting })=> submitForm({values, setSubmitting})> {()=>(//...do something here)}

Erbes answered 5/6, 2021 at 4:40 Comment(0)
L
0

Another simple approach is to useState and pass prop to the child formik component. There you can setState with a useEffect hook.

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

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

   return <></>
 }
Lorelle answered 12/10, 2021 at 12:10 Comment(0)
M
0

I've implemented this in a React Class component, step by step :

1 - I've declared a "ref" variable to keep reference of form object, (useRef is valid only in function components so I've coded as below by using React.createRef() function )

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

2 - There is an "innerRef" feature on formik forms, so I've assigned the ref variable above to it :

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

3- To trigger the submit event of the form, from somewhere out of the form I have declared a function below :

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

4- And finally I've called the function above from an external button :

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

Note not to confuse : onSubmit(values) function which assined to the formik form, is still exists and it's getting the from values. We've just triggered it from an external button here

Marker answered 6/4, 2022 at 14:35 Comment(0)
E
0

Here is how I achieved mine by using ref to simulate an internal hidden submit button being clicked when the user clicks the external submit button.

const hiddenInnerSubmitFormRef = useRef(null);

const handleExternalButtonClick = () => {
  hiddenInnerSubmitFormRef.current.click();
}

return (
<>
  {/* External Button */}
  <button onClick={handleExternalButtonClick}>
   External Submit
  </button>

  <Formik onSubmit={values => console.log(values)}>
   {() => (
     <Form>

      {/* Hide Button /*}
      <button type="submit" ref={hiddenInnerSubmitFormRef} className="hidden">
        Submit
      </button>
     </Form>
   )}
  </Formik>
 </>
)
Evers answered 23/6, 2022 at 8:38 Comment(3)
For functional component, this answer will trigger error: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? so better use the other solution in this page: https://mcmap.net/q/235180/-react-formik-use-submitform-outside-lt-formik-gtMunsey
@Munsey This was the exact method I used and it didn't trigger any errorEvers
2 possibilities: either you don't use functional component, or you use older react version than mineMunsey

© 2022 - 2024 — McMap. All rights reserved.