jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.87k stars 2.78k forks source link

How to check FormikContext provider without warnings? #2338

Open verbart opened 4 years ago

verbart commented 4 years ago

🚀 Feature request

My component should be able to work with the formik and without it. I use hook useFormikContext to check if the component is in the formik. I do this consciously, so that the warnings are not relevant and interfere.

Current Behavior

...
const isFormikField = !!useFormikContext();
...

Warning: Formik context is undefined, please verify you are calling useFormikContext() as child of a <Formik> component.

The same goes for connect HOC:

import { connect as formikConnect, Field as FormikField } from 'formik';

return formikConnect(class FormField extends React.Component {
  render() {
    const { formik, ...props } = this.props;
    const isFormikField = !!formik;

    return (
      isFormikField ?
        <FormikField
            ...

Warning: Formik context is undefined, please verify you are rendering <Form>, <Field>, <FastField>, <FieldArray>, or your custom context-using component as a child of a <Formik> component.

Desired Behavior

How to get rid of warning?

Suggested Solution

Perhaps they are not so needed, with deliberate use, and can they be deleted?

Who does this impact? Who is this for?

For all users who consciously use verification in custom components

LeathanTeal commented 3 years ago

@verbart I have the same issue. Have you found a solution for checking if a component is within a Formik context?

verbart commented 3 years ago

@LeathanTeal At the moment I will pass the prop for verification: image

LeathanTeal commented 3 years ago

@verbart That workaround makes sense. Thanks for your reply. Wish there was a option built in to Fromik to gracefully handle these situations.

johnrom commented 3 years ago

This isn't how I'd recommending connecting to Formik. Formik's context should always be used under a FormikProvider. I'd recommend you decide whether to connect to Formik or not using multiple HoCs, one which connects to Formik and one which doesn't. Your workaround violates the Rules of Hooks, conditionally calling hooks.

The reason I recommend using another method is because it should be clear where your context comes from just by glancing at the code. This is clear:

const MyFormikForm = () => <Formik><FormikTextField name="myField" /></Formik>;
const MyMockForm = () => <form><MockTextField name="myField" /></form>

If instead, you do:

const MyFormikForm = () => <Formik><TextField name="myField" /></Formik>;
const MyMockForm = () => <form><TextField name="myField" /></form>

It will never be clear where context should come from at any given time, and it will make unit testing insane. For example, how do you know MyMockForm wasn't nested under another component with Formik? It requires knowledge of the entire app, which is one concern that React actively tries to solve.

Instead, you can use your own custom Context, like this:

const MyContext = React.createContext({
  setValue: null,
  setTouched: null,
  // ...etc
});

const MyFormikConnectedFormWrapper = ({ children, formikProps }) => {
  const formik = useFormik(props.formikProps);

  return <MyContext.Provider value={{
    setValue: formik.setFieldValue,
    setTouched: formik.setFieldTouched,
  }}>
    {props.children}
  </MyContext>
}

const MyNonFormikFormWrapper = ({ children }) => {
  const setValue = useCallback((name, value) => console.log('changed: ', name, value), []);

  return <MyContext.Provider value={{
    setValue,
    // etc
  }}>
    {props.children}
  </MyContext>
}

const DualForms = () => 
  <>
    <MyFormikConnectedFormWrapper initialValues={{ myField: '' }}>
      <MyField name="myField" />
    </MyFormikConnectedFormWrapper>
    <MyNonFormikFormWrapper>
      <MyField name="myField" />
    </MyNonFormikFormWrapper>
  </>;

const MyField = (props) => {
  const formContext = React.useContext(MyContext);

  <input name={props.name} onChange={event => formContext.setValue(props.name, event.target.value)} />
}

In DualForms above, it's easy to see where your context is coming from for MyField at all times regardless of what the hierarchy of the rest of your app is.

matewka commented 3 years ago

@verbart I've had the same problem today.

In our project we're trying to introduce Formik as a replacement for redux-form. To be able to refactor code gradually and seamlessly, I wanted to keep the existing HOC which we're using for redux-form fields.

Here's an idea that I came up with:

Why not use formik's context directly, instead of via hook? It is the hook itself that throws the warning. Fortunately, formik package exports the "raw" context as well.

Here's my (simplified) code:

import React, { useContext } from 'react'
import { FormikContext } from 'formik'

// This is a HOC that we've been using so far for decorating form components for usage with redux-form
const forField = (Component) => {
  return (props) => {
    // MAGIC HAPPENS HERE
    const formikContext = useContext(FormikContext)

    if (formikContext) {
      const [field] = useField(...)
      return <Component ... />
    }

    return <Component ... />
  }
}

I haven't tested it yet in a real-case scenario but I can already confirm that the approach is the right one, because:

useContext(FormikContext) === useFormikContext() // true
omergal99 commented 3 years ago

Tnx! it wokrs

TechSaq commented 2 years ago

@verbart I've had the same problem today.

In our project we're trying to introduce Formik as a replacement for redux-form. To be able to refactor code gradually and seamlessly, I wanted to keep the existing HOC which we're using for redux-form fields.

Here's an idea that I came up with:

Why not use formik's context directly, instead of via hook? It is the hook itself that throws the warning. Fortunately, formik package exports the "raw" context as well.

Here's my (simplified) code:

import React, { useContext } from 'react'
import { FormikContext } from 'formik'

// This is a HOC that we've been using so far for decorating form components for usage with redux-form
const forField = (Component) => {
  return (props) => {
    // MAGIC HAPPENS HERE
    const formikContext = useContext(FormikContext)

    if (formikContext) {
      const [field] = useField(...)
      return <Component ... />
    }

    return <Component ... />
  }
}

I haven't tested it yet in a real-case scenario but I can already confirm that the approach is the right one, because:

useContext(FormikContext) === useFormikContext() // true

Thanks, buddy. It works like a charm

tolunayozdemir commented 1 year ago

@verbart I've had the same problem today.

In our project we're trying to introduce Formik as a replacement for redux-form. To be able to refactor code gradually and seamlessly, I wanted to keep the existing HOC which we're using for redux-form fields.

Here's an idea that I came up with:

Why not use formik's context directly, instead of via hook? It is the hook itself that throws the warning. Fortunately, formik package exports the "raw" context as well.

Here's my (simplified) code:

import React, { useContext } from 'react'
import { FormikContext } from 'formik'

// This is a HOC that we've been using so far for decorating form components for usage with redux-form
const forField = (Component) => {
  return (props) => {
    // MAGIC HAPPENS HERE
    const formikContext = useContext(FormikContext)

    if (formikContext) {
      const [field] = useField(...)
      return <Component ... />
    }

    return <Component ... />
  }
}

I haven't tested it yet in a real-case scenario but I can already confirm that the approach is the right one, because:

useContext(FormikContext) === useFormikContext() // true

thanks, it works.

rottitime commented 3 months ago

I took a slightly different approach as I do not think the warning should be there if within out code with are checking that the hook is defined (e.g. like you had done !!useFormikContext()).

So I have hidden this warning on my Jest tests from the global setup

// src/setupTests.ts

(function () {
  const filters = [
    "Warning: Formik context is undefined, please verify you are calling useFormikContext() as child of a <Formik> component."
  ]; //array of warning messages to hide
  const { warn } = console;

  console.warn = function (msg, ...args) {
    filters.some((filter) => msg.includes(filter))
      ? jest.fn()
      : warn.apply(console, [msg, ...args]);
  };
})();