Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v2] <Formik /> ref prop #1603

Closed
peterwiebe opened this issue Jun 12, 2019 · 22 comments 路 Fixed by #1972
Closed

[v2] <Formik /> ref prop #1603

peterwiebe opened this issue Jun 12, 2019 · 22 comments 路 Fixed by #1972

Comments

@peterwiebe
Copy link

peterwiebe commented Jun 12, 2019

馃殌 Feature request

Current Behavior

Since switching <Formik /> to a function component if you try to attach a ref to it as such:

<Formik ref={myRef} /> 

you will get the following error:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Desired Behavior

The desired behaviour is for there to be no error and the ref to provide a set of APIs to programmatically interact with <Formik />.

Suggested Solution

To enable the functionality it seems that the hook useImperativeHandle will be needed to wrap <Formik/> and expose the available methods.

export const Formik = forwardRef((props, ref) => {
  const formikRef = useRef()
  useImperativeHandle(ref, () => ({
    submitForm: () => handleSumbit()
  })
  
  return (
    <FormikProvider ref={formikRef} value={formikbag}>
      {...}
    </FormikProvider>
  )
})

An example per the docs

Who does this impact? Who is this for?

This is for anyone who needs to implement any programmatic functionality on .

Describe alternatives you've considered

Use the prior version where is a class component.

Additional context

I was trying to implement the debounced autosave that was handled by the parent component, without the need for a specific component that is a child of .

@ComLock
Copy link

ComLock commented Aug 1, 2019

I guess this applies to withFormik aswell?

I have two forms similar to:
https://codesandbox.io/s/formik-multiple-form-in-one-submission-kbf1w?fontsize=14

I would like to make a reset button that runs formik.resetForm() on both forms.

I can get a reference to the withFormik react node, but not child which is the actual Formik react node (which I'm guessing provides the formikBag API I need).

Ideas?

@tsuby
Copy link

tsuby commented Oct 8, 2019

Have you made any progress with this? I'm facing a similar problem.

@stale stale bot removed the stale label Oct 8, 2019
@chris-gunawardena
Copy link

chris-gunawardena commented Oct 25, 2019

I managed to get this to work by passing in the ref as a propery.
const formikFormRef = useRef(null); //in parent
<FormikForm formikFormRef={formikFormRef} ></FormikForm> // in parent

Then inside the form

const FormikForm = (withFormik({ enableReinitialize: true})((props, values) => {
  useImperativeHandle(props.formikFormRef, () => ({
    autoSave: () => {
      console.log('xxx');
    }
  }));

Now I can call this from the parent with:

  useEffect(() => {
        setInterval(() => {
          formikFormRef.current.autoSave()
        }, 3000);
      });
  });

@brunohkbx
Copy link
Contributor

Any updates?

@elkinjosetm
Copy link

Any updates on this, I can't upgrade to v2 because I have to reset some forms in my app using the reference from outside the form itself

@jaredpalmer
Copy link
Owner

PR's welcome.

@brunohkbx
Copy link
Contributor

I would like to be able to implement this, but I don't know how to fix the interface FormikConfig to handle the type of the ref using forwardRef 馃槥

@chris-gunawardena
Copy link

chris-gunawardena commented Oct 29, 2019

Do we really need this? While you can't do <Formik ref={myRef} /> passing it as <Formik myRef={myRef} /> works. Then you can subscribe to it using useImperativeHandle(props.myRef, () => {}) from within the form and call it from outside as myRef.current.fooBar(). See my previous post for full example.

@brunohkbx
Copy link
Contributor

Hey guys. Do you have any thoughts on @johnrom suggestion?
Please, check this PR: #1972

@MikeSuiter
Copy link

How is this for a work-around? Our code is still on v1 and it works but I haven't tested with v2.

  render() {
    const { children, ...props } = this.props;

    return (
      <Formik {...props}>
        {formikProps => {
          this.formikProps = formikProps;
          return this.renderChildren(formikProps);
        }}
      </Formik>
    );
  }

@Crownie
Copy link

Crownie commented Nov 22, 2019

Here is a workaround that works for me in v2.

  • I created a component; FormikWithRef
  • The component forwards all the props to the original Formik component.
  • It then exposes the formikProps using useImperativeHandle
// FormikWithRef.tsx

import React, {forwardRef, useImperativeHandle} from 'react';
import {Formik, FormikProps, FormikConfig} from 'formik';

function FormikWithRef(props: FormikConfig<any>, ref) {
    let _formikProps: FormikProps<any>;

    useImperativeHandle(ref, () => (_formikProps));

    return (
        <Formik {...props}>
            {(formikProps) => {
                _formikProps = formikProps;
                if (typeof props.children === 'function') {
                    return props.children(formikProps);
                }
                return props.children;
            }}
        </Formik>
    );
}

export default forwardRef(FormikWithRef)

Then I use the new component; FormikWithRef instead of Formik;

// LoginPage.tsx

import {useRef} from 'react';
import {FormikProps} from 'formik';

export default function LoginPage() {
    const myFormRef = useRef<FormikProps<any>>(null);

    return <FormikWithRef ref={myFormRef}>
        {({
              values,
              errors,
              touched,
              handleChange,
              handleBlur,
              handleSubmit,
              isSubmitting,
          }) => (
            <form onSubmit={handleSubmit}>
                // ...
            </form>
        )}
    </FormikWithRef>
}

@danimbrogno-pml
Copy link

What's the status of this? I see this was closed, and I can see useImperativeHandle in the codebase in 2.1.2, but I can't pass a ref into the component.

I get the TS error: Property 'ref' does not exist on type 'IntrinsicAttributes & FormikConfig<...>

@johnrom
Copy link
Collaborator

johnrom commented Jan 17, 2020

You can get a ref to formikBag via innerRef prop.

const bagRef = useRef();

<Formik innerRef={bagRef} />

Note: this isn't a reference to the Formik component, but to the "bag" of formik tools. like handleSubmit et all. I think using ref prop was avoided so that in the future if there's a reason to add a real ref, we can.

@TeoTN
Copy link

TeoTN commented Jan 18, 2020

To me this looks like an undocumented breaking change that should be included in "Migrating to v2" of docs.

It is also poorly typed, the innerRef is of type (instance: any) => void

@johnrom
Copy link
Collaborator

johnrom commented Jan 20, 2020

@TeoTN it is quite poorly typed 馃憥 . We're discussing a real implementation here: #2208

@diosney
Copy link

diosney commented Apr 16, 2020

@johnrom What would be the classes equivalent?

I'm trying with useRef but it seems that it is intended only for function hooks, and createRef with ref doesn't work either.

For Formik slug being "without tears", I already have dropped a lot :(

@johnrom
Copy link
Collaborator

johnrom commented Apr 16, 2020

@diosney useRef is specific to functional "React hooks". functional components are generally considered preferable to classes in many scenarios except some very complex use cases that functional components cannot be used for. however, for backwards compatibility the class-based ref method is

class MyClass = {
  public constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return (
      <Formik innerRef={this.myRef} />
    );
  }
}

Note, besides using innerRef instead of ref, this has nothing to do with Formik, and those are just plain ol' React tears.

docs link

@diosney
Copy link

diosney commented Apr 16, 2020

@johnrom Thanks for your answer and sorry for my grunt, but I have several rounds on this already and tested lot of things without making it working as it should, I'm really just about pulling my off my hair.

I already tried your solution with createRef, even repeated using constructor since I do not do it like that (using typescript sugar classes) but it keeping outputing:

{"current": null}

and any call to this.myRef.errors or this.myRef.current.errors or any other Formik "bag" property gives an error.

The only way I could get some partial success was by something like:

 {(formikProps) => {
                self.formSubmit  = formikProps.handleSubmit;
                self.formIsValid = formikProps.isValid;
                self.formErrors  = formikProps.errors;
                ....

but for some wicked reason it only works if I made some change in the code while developing and having Hot Reload enabled.

Have any other suggestions?

Really don't know why it is not working, I updated Formik to 2.1.4 from 2.1.1 and all remains the same.

I'm trying to use those values on a Button below and outside the Formik component.

@johnrom
Copy link
Collaborator

johnrom commented Apr 16, 2020

@diosney if you open a new issue someone may be able to help you there. during issue creation, you should try and reproduce your issue in a CodeSandbox (link to the sandbox starter is in the new issue creation template). You might figure out where the issue is during reproduction (happens a lot!), or if you are able to reproduce we'll be able to look immediately and see where the problem is.

@diosney
Copy link

diosney commented Apr 16, 2020

@johnrom OK, I understand, thanks for your time.

@mstoil
Copy link

mstoil commented Jan 4, 2021

Hi @johnrom, hi all

Note: this isn't a reference to the Formik component, but to the "bag" of formik tools. like handleSubmit et all. I think using ref prop was avoided so that in the future if there's a reason to add a real ref, we can.

I've tried using the innerRef to get to the resetForm. I'm doing:

import React, { useRef } from 'react';
...
const form1Ref = useRef(null);

<Formik 	innerRef={form1Ref} ...

then when trying to use it for a reset, the correct values are present but the reset (even that it passes the new values when debbuged inside formik resetForm fn.) doesn't take effect

form1Ref.current.resetForm({ values: { ...form1Ref.current.values }})

I guess it has to be combined as some suggestions with useimperativehandle.

Another approach I've tried is using the useFormikContext:

	const ResetForm1 = () => {
		const ctx = useFormikContext();
		if (isSubmittingForm1) {
			ctx.resetForm({ values: { ...ctx.values }, isSubmitting: false });
			setIsSubmittingForm1(false);
		}
		return null;
	};

Then using this down in the Formik form:

<Formik
    initialValues={{
        ...initial values for each of the fields
    }}
    //onSubmit={handleSubmit}
>
    {({ values, dirty }) => (
        <>
            {dirty !== dirtyFormFlag[0] && setDirtyFormFlag(0, dirty)}
            <ResetForm1 />
            <form key="form1" className="px-16">
                                                    .... some fields in here
            </form>
        </>
    )}
</Formik>

I'm setting a variable isSubmittingForm1 in order to decouple the submitting funcitonality for multiple forms (using Formik) and set it on a single button.
The idea is that the forms might be dynamic (one or maybe more forms, not always the same), so I wanna controll the submitting button being disabled if there isn't any dirty from, and for this - I need to reset all forms after a custom submit (happening out of the scope of each of the forms, so in the parrent container page).

Update: I've managed to do the reset with keeping the current Initial values in a state, and then passing that state object
in the <Formik enableReinitialize={true} initialValues={{ here the state controlled vals.}}. On each submit, I'm updating the state - current initial Values and having the enableReinitialize={true} it's as if the form has reseted.

Not liking this... but it's a work arround.

@Evaldoes
Copy link

To access formik by reference, you can do like this:

const formRef = useRef<FormikProps<FormProps>>(null);

<Formik innerRef={formRef} />

  • FormProps iis the model of the data that you will use in the form

image

image

If you print the reference console.log(formRef.current) you will see something like this:

image

This way it is possible to manipulate formik from anywhere 馃槃 馃槑 that's it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.