Реагировать Formik использовать submitForm за пределами <Formik />

Текущее поведение

<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, но оно всегда возвращает меня Uncaught TypeError: _this.props.onSubmit is not a function

Когда я пытался console.log(this.form) есть submitForm функция.

Любое решение, ребята?


- Версия Formik: последняя - Версия React: v16 - ОС: Mac OS

13 ответов

Решение

Нашел виновника.

Больше нет onSubmitCallback на формике реквизит. Следует изменить его на onSubmit

Просто для тех, кто задается вопросом, каково решение с помощью хуков React:

Formik 2.x, как описано в этом ответе

// 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 опора

TL; DR; Этот контекстный ответ работает как шарм, если компонент, который вы отправляете, является дочерним по отношению к<Formik> или 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} />

Вы можете связать 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;

У меня была такая же проблема, и я нашел очень простое решение, надеюсь, это поможет:

Проблему можно решить простым html. Если поставить id тег на вашем, тогда вы можете настроить его с помощью кнопки, используя кнопку form тег.

пример:

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

Вы можете разместить форму и кнопку где угодно, даже отдельно.

Эта кнопка затем активирует функцию отправки форм, и formik зафиксирует, что продолжит процесс как обычно. (пока форма отображается на экране, пока отображается кнопка, это будет работать независимо от того, где находятся форма и кнопка)

Лучшее решение, которое я нашел, описано здесь /questions/34019641/kak-otpravit-formu-s-knopki-vne-etogo-komponenta-v-react/55216516#55216516

Копируем ответ сюда:

Добавьте в форму атрибут id: id = 'my-form'

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

Затем добавьте тот же идентификатор в атрибут "form" целевой кнопки вне формы:

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

Теперь кнопка «Внешняя кнопка» будет абсолютно такой же, как если бы она находилась внутри формы.

Примечание. Это не поддерживается IE11.

В 2021 году этот способ сработал для меня, чтобы удовлетворить несколько требований:

  • Кнопки отправки / сброса не могут быть вложены в элемент. Обратите внимание: если вы можете это сделать, вам следует использовать useFormikContextответ, потому что он проще моего. (Моя позволит вам изменить, какая форма отправляется (у меня одна панель приложения, но несколько форм, к которым пользователь может перейти).
  • Внешние кнопки отправки / сброса должны иметь возможность отправлять и сбрасывать форму Formik.
  • Внешние кнопки отправки / сброса должны отображаться отключенными до тех пор, пока форма не станет грязной (внешний компонент должен иметь возможность наблюдать за состоянием формы Formik).

Вот что я придумал: я создал новый поставщик контекста, предназначенный для хранения некоторых полезных материалов Formik, чтобы связать два моих внешних компонента, которые находятся в разных вложенных ветвях приложения (глобальная панель приложения и форма где-то еще, глубже на странице view - на самом деле мне нужны кнопки отправки / сброса, чтобы адаптироваться к различным формам, к которым пользователь перешел, а не только к одному; не только к одному элементу, а только по одному за раз).

Вы размещаете <FormContextProvider>достаточно высока в вашем приложении, чтобы обертывать оба разрозненных компонента, которым необходим доступ к материалам 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>>
  dirty: boolean
  setDirty: (value: boolean) => 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 [dirty, setDirty] = useState<boolean>(false)

  const store: ContextProps = {
    formikFormRef,
    dirty,
    setDirty,
  }

  return <FormContext.Provider value={store}>{children}</FormContext.Provider>
}

В компоненте, который отображает элемент, я добавляю эту строку:

      const { formikFormRef, setDirty } = useContext(FormContext)

В том же компоненте я добавляю к элементу этот атрибут:

      innerRef={formikFormRef}

В этом же компоненте первое, что вложено в <Formik> этот элемент (важно, обратите внимание на добавление setDirty(dirty) линия).

Обновление: к сожалению, мы не можем позвонить setDirtyздесь; мы получим ошибку о попытке установить в контексте при рендеринге :(

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

    return (
      <Form>
       ...

В моем компоненте, который содержит кнопки отправки / сброса, у меня есть следующее. Обратите внимание на использование formikFormRef и

      export const MyAppBar: React.FC<Props> = ({}) => {
  const { formikFormRef, dirty } = useContext(FormContext)
  

  return (
    <>
      <AppButton
        onClick={formikFormRef.current?.resetForm}
        disabled={!dirty}
      >
        Revert
      </AppButton>
      <AppButton
        onClick={formikFormRef.current?.submitForm}
        disabled={!dirty}
      >
        Save
      </AppButton>
    </>
  )
}

В refполезен для вызова методов Formik, но не может быть отслежен для его свойства (response не запускает повторную визуализацию для этого изменения). Вот почему dirty свойство хранится с использованием useState крюк.

Спасибо, я черпал вдохновение из других ответов, чтобы найти способ удовлетворить все мои собственные требования.

Вам не нужно использовать ref, кастомный хук useFormikContext предоставить способ использовать submit в любом месте дерева, при одном условии, что корневой компонент должен быть компонентом formik:

   const { values, submitForm } = useFormikContext();

Если вы используете withFormik, у меня это сработало:

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

Просто поместите в форму регулярную ссылку на реакцию:

        <form
        ref={newFormRef}
        
        onSubmit={handleSubmit}
      >

Я построил пример на основе хуков.

https://codesandbox.io/s/formik-submit-button-outside-form-d825u

Можешь попробовать

const submitForm = ({values, setSubmitting}) => {// ... здесь что-то делаем}

<Formik onSubmit = {(values, {setSubmitting}) => submitForm({values, setSubmitting})> {() => (// ... здесь что-то делаем)}

Другой простой подход - использоватьState и передать свойство дочернему компоненту formik. Там вы можете установитьState с помощью хука useEffect.

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

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

   return <></>
 }

Я реализовал это в компоненте класса React шаг за шагом:

1 — я объявил переменную «ref», чтобы сохранить ссылку на объект формы (useRef действителен только в функциональных компонентах, поэтому я закодировал, как показано ниже, с помощью функции React.createRef())

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

2 - В формах formik есть функция "innerRef", поэтому я присвоил ей переменную ref выше:

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

3- Чтобы вызвать событие отправки формы, откуда-то из формы я объявил функцию ниже:

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

4- И, наконец, я вызвал функцию выше с внешней кнопки:

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

Обратите внимание, чтобы не перепутать: функция onSubmit(values), которая назначена форме formik, все еще существует и получает значения from. Мы только что активировали его с помощью внешней кнопки.

Вот как я добился своего, используя ref для имитации нажатия внутренней скрытой кнопки отправки, когда пользователь нажимает внешнюю кнопку отправки.

      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>
 </>
)
Другие вопросы по тегам