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

useFormikContext is throwing undefined when used along withFormik #2724

Closed
varunrayen opened this issue Aug 28, 2020 · 8 comments
Closed

useFormikContext is throwing undefined when used along withFormik #2724

varunrayen opened this issue Aug 28, 2020 · 8 comments

Comments

@varunrayen
Copy link

馃悰 Bug report

Current Behavior

const { values, submitForm } = useFormikContext();

Throws the following error

TypeError: Cannot destructure property 'values' of 'Object(...)(...)' as it is undefined.

Expected behavior

To get the values from Formik form

Your environment

Software Version(s)
Formik 2.1.5
React 0.0.0-experimental-94c0244ba
Browser Firefox
npm/Yarn 6.14.5
Operating System MacOS
@punmechanic
Copy link

Hi @varunrayen,

This is intentional behaviour if there is no <Formik> component in the tree above the caller of useFormikContext(). I couldn't replicate your issue when using Formik as intended when using the versions of React and Formik you've listed.

Could you provide a codesandbox that replicates your error?

@davidhousedev
Copy link

For what it's worth, the behavior that the absence of a Formik context should return undefined caught me by surprise as well. Given that I will commonly destructure attributes from hooks that return an object, I would have expected that the hook would have returned an empty object rather than undefined.

Additionally, if I'm reading the types properly I believe the TS types do not reflect this possibility, so even running TS with --strictNullChecks may not flag this as a聽potential case to handle.

Have you all considered returning an empty object instead of undefined?

@johnrom
Copy link
Collaborator

johnrom commented Oct 5, 2020

You should receive an error message when using useFormikContext without a context in the hierarchy. I don't see a reason to add an empty object here when your app has entered an exceptional state. Further, an empty object would be a lie. There is no context, so returning an empty object would lead to a false sense of security.

You can instead write something like const { handleChange } = useFormikContext() ?? {};

The main problem with updating the types to reflect that undefined is a possibility is that code that works properly will now need to do conditional checks for everything in TypeScript. See:

const MyForm = () => {
  <Formik><MyFields /></Formik>
}

const MyFields = () => {
- const { handleChange, submitForm, touched, errors, values } = useFormikContext(); // ERROR formik is possibly undefined
+ const { handleChange, submitForm, touched, errors, values } = useFormikContext() ?? {};

  return <>
    { /* OK */ }
    <form onSubmit={submitForm}>
      <label htmlFor="myField">My Field</label>
      <input 
        id="myField"
        name="myField" 
        { /* OK, I guess */ }
        value={values?.myField}
        { /* this is bad */ }
        onChange={handleChange ? handleChange('myField') : undefined}
      />
      { /* that's a lot of question marks */ }
      {touched?.myField && errors?.myField && <p>{errors?.myField}</p>}
    </form>
  </>
}

Interestingly, the only fix for this would be to remove your ability to destructure right away:

const MyFields = () => {
  const formik = useFormikContext();

  if (!formik) {
    throw "Error loading form";
  }

  const { handleChange, submitForm, touched, errors, values } = formik;

  return <OK />
}

I think that's actually a pretty nice API, or at the very least a more in tune with the way React Context itself works. But every bit of code that's ever been written in TypeScript using useFormikContext would be broken if we made this change.

@punmechanic
Copy link

punmechanic commented Oct 5, 2020

I assume the reason why useFormikContext() does not throw is so that users can identify if they are within a Formik tree. If this is not considered a common use-case, then I would recommend an approach similar to React Router, where useFormikContext() would throw if no context is found. This would also "fix" the TypeScript typings such that undefined could never be returned:

export function useFormikContext<T>(): Context<T> | never;

This would indicate to the caller that useFormikContext() would always return a Context<T> or it would never return (i.e it would throw).

This would also be a breaking change, though.

I am personally a fan of using a meaningful zero value i.e, returning an empty object with meaningful defaults if there is nothing further up the tree, but this is not a common pattern in React/JavaScript, as much as I wish it was.

@johnrom
Copy link
Collaborator

johnrom commented Oct 5, 2020

It isn't a common pattern because nothingness is meaningful. There are two different nothings in JavaScript, that is how meaningful nothingness is. Many backend libraries lean towards useful defaults and throwables for very good reasons. In the backend, if a variable you expect doesn't exist, you are likely to throw. Nothingness is instead an instantiated value that is assigned to null.

Throwing is nice when you have a backend because you catch it in your logging and exceptional code is part of the ongoing iteration. Exceptions also help protect critical code from being reached with exceptional states.

On the client side, both of these patterns are a little less intuitive. Firstly, you don't want to interrupt your user. One missed catch on the front-end might completely bork your frontend and make your site unusable. Many people do not have comprehensive client-side analytics set up, so they might not notice until a customer complained about their website not working. If Formik is the reason code is throwing all over the internet, we'll never hear the end of it.

Second, in many languages the concept of nothingness is almost an afterthought. Null is a positive value meaning nothing, whereas undefined is the absence of anything. JavaScript is a nothingness-forward language because of its use in the browser, where variables you are looking for just may completely not exist, like the return value of useFormikContext without a context over it. In this case, undefined is the perfect value representing the state of Formik's context.

Meaningful defaults can also be useful, and I agree with them, but they have to be meaningful and not a lie. A fake formik context isn't meaningful to me, and it would make it more complicated to detect whether or not there is a context above. To me, it would be a lie.

Interestingly, the exact opposite issue exists on this repository, as well. Hopefully it shows that we walk a pretty balanced line with a lot of different perspectives (and yours is appreciated!) #2338

TL:dr; user wants check if useFormikContext returns undefined, and if so, do something else.

@github-actions
Copy link
Contributor

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

@johnrom
Copy link
Collaborator

johnrom commented Nov 20, 2020

@varunrayen hopefully my explanation above helped solidify why we'll continue to return undefined.

As explained above you can do this to fix your original issue. If using typescript you'll probably have to do some extra finessing.

const { values, submitForm } = useFormikContext() ?? {};

@johnrom johnrom closed this as completed Nov 20, 2020
@PiTiLeZarD
Copy link

Just for whomever might be reading this: I had a component with a different version of formik than the main project in a monorepo and that was giving me this error. I aligned the versions and that fixed it.

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

No branches or pull requests

5 participants